acknowledgements.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // Acknowledgements page
  2. //
  3. // Various code from:
  4. // https://github.com/flutter/flutter/blob/efb1346767e67d2577461baf671eb2d5d2c4bb90/packages/flutter/lib/src/material/about.dart
  5. import 'package:flutter/foundation.dart';
  6. import 'package:flutter/widgets.dart';
  7. import 'package:flutter/material.dart'
  8. show
  9. CircularProgressIndicator,
  10. Divider,
  11. ListTile,
  12. Material,
  13. MaterialPageRoute,
  14. Theme;
  15. import '../ui/adaptive.dart' show AppScaffold;
  16. import '../l10n/app_localizations.dart';
  17. class AcknowledgementsPage extends StatefulWidget {
  18. const AcknowledgementsPage({
  19. super.key,
  20. });
  21. @override
  22. State<AcknowledgementsPage> createState() => _AcknowledgementsPageState();
  23. }
  24. /// Acknowledgements page for used libraries.
  25. class _AcknowledgementsPageState extends State<AcknowledgementsPage> {
  26. // Adapted from flutter/flutter
  27. final Future<_LicenseData> licenses = LicenseRegistry.licenses
  28. .fold<_LicenseData>(
  29. _LicenseData(),
  30. (_LicenseData prev, LicenseEntry license) => prev..addLicense(license),
  31. )
  32. .then((_LicenseData licenseData) => licenseData..sortPackages());
  33. @override
  34. Widget build(BuildContext context) {
  35. return AppScaffold(
  36. title: Text(AppLocalizations.of(context)!.licenses),
  37. body: FutureBuilder<_LicenseData>(
  38. future: licenses,
  39. builder: (BuildContext context, AsyncSnapshot<_LicenseData> snapshot) {
  40. return LayoutBuilder(
  41. key: ValueKey<ConnectionState>(snapshot.connectionState),
  42. builder: (BuildContext context, BoxConstraints constraints) {
  43. switch (snapshot.connectionState) {
  44. case ConnectionState.done:
  45. if (snapshot.hasError) {
  46. return Center(child: Text(snapshot.error.toString()));
  47. }
  48. return Material(
  49. child: ListView.separated(
  50. itemCount: snapshot.data!.count(),
  51. separatorBuilder: (BuildContext context, int index) =>
  52. const SizedBox(height: 0, width: 0),
  53. itemBuilder: (BuildContext context, int index) {
  54. return ListTile(
  55. dense: true,
  56. title: Text(snapshot.data!.getTitle(index)),
  57. onTap: () {
  58. Navigator.of(context).push(
  59. MaterialPageRoute(
  60. builder: (context) => FullScreenLicense(
  61. snapshot.data!.getTitle(index),
  62. snapshot.data!.getLicenses(index),
  63. ),
  64. ),
  65. );
  66. },
  67. );
  68. },
  69. ),
  70. );
  71. case ConnectionState.none:
  72. case ConnectionState.active:
  73. case ConnectionState.waiting:
  74. return Material(
  75. color: Theme.of(context).cardColor,
  76. child: const Center(child: CircularProgressIndicator()),
  77. );
  78. }
  79. },
  80. );
  81. },
  82. ),
  83. );
  84. }
  85. }
  86. /// Full screen license widget
  87. class FullScreenLicense extends StatelessWidget {
  88. /// The name
  89. final String title;
  90. /// License
  91. final List<LicenseEntry> licenseEntries;
  92. const FullScreenLicense(this.title, this.licenseEntries, {super.key});
  93. @override
  94. Widget build(BuildContext context) {
  95. List<Widget> licenses = <Widget>[];
  96. for (var (index, licenseEntry) in licenseEntries.indexed) {
  97. if (index != 0) {
  98. licenses.add(const Divider());
  99. }
  100. licenses.addAll(licenseEntry.paragraphs.map((p) {
  101. // Adapted from flutter/flutter
  102. if (p.indent == LicenseParagraph.centeredIndent) {
  103. return Padding(
  104. padding: const EdgeInsets.only(top: 16.0),
  105. child: Text(
  106. p.text,
  107. style: const TextStyle(fontWeight: FontWeight.bold),
  108. textAlign: TextAlign.center,
  109. ),
  110. );
  111. } else {
  112. return Padding(
  113. padding:
  114. EdgeInsetsDirectional.only(top: 8.0, start: 16.0 * p.indent),
  115. child: Text(p.text),
  116. );
  117. }
  118. }));
  119. }
  120. return AppScaffold(
  121. title: Text(title),
  122. body: Material(
  123. child: SingleChildScrollView(
  124. child: Padding(
  125. padding: const EdgeInsets.only(left: 12, right: 12, bottom: 12),
  126. child: Column(children: licenses),
  127. ),
  128. ),
  129. ),
  130. );
  131. }
  132. }
  133. /// Adapted from flutter/flutter
  134. /// With renames and addition of helper methods.
  135. ///
  136. /// This is a collection of licenses and the packages to which they apply.
  137. /// [_packageLicenseBindings] records the m+:n+ relationship between the license
  138. /// and packages as a map of package names to license indexes.
  139. class _LicenseData {
  140. final List<LicenseEntry> _licenses = <LicenseEntry>[];
  141. final Map<String, List<int>> _packageLicenseBindings = <String, List<int>>{};
  142. final List<String> _packages = <String>[];
  143. // Special treatment for the first package since it should be the package
  144. // for delivered application.
  145. String? firstPackage;
  146. void addLicense(LicenseEntry entry) {
  147. // Before the license can be added, we must first record the packages to
  148. // which it belongs.
  149. for (final String package in entry.packages) {
  150. _addPackage(package);
  151. // Bind this license to the package using the next index value. This
  152. // creates a contract that this license must be inserted at this same
  153. // index value.
  154. _packageLicenseBindings[package]!.add(_licenses.length);
  155. }
  156. _licenses.add(entry); // Completion of the contract above.
  157. }
  158. /// Add a package and initialize package license binding. This is a no-op if
  159. /// the package has been seen before.
  160. void _addPackage(String package) {
  161. if (!_packageLicenseBindings.containsKey(package)) {
  162. _packageLicenseBindings[package] = <int>[];
  163. firstPackage ??= package;
  164. _packages.add(package);
  165. }
  166. }
  167. /// Sort the packages using some comparison method, or by the default manner,
  168. /// which is to put the application package first, followed by every other
  169. /// package in case-insensitive alphabetical order.
  170. void sortPackages([int Function(String a, String b)? compare]) {
  171. _packages.sort(compare ??
  172. (String a, String b) {
  173. // Based on how LicenseRegistry currently behaves, the first package
  174. // returned is the end user application license. This should be
  175. // presented first in the list. So here we make sure that first package
  176. // remains at the front regardless of alphabetical sorting.
  177. if (a == firstPackage) {
  178. return -1;
  179. }
  180. if (b == firstPackage) {
  181. return 1;
  182. }
  183. return a.toLowerCase().compareTo(b.toLowerCase());
  184. });
  185. }
  186. int count() {
  187. return _packages.length;
  188. }
  189. String getTitle(int index) {
  190. return _packages[index];
  191. }
  192. List<LicenseEntry> getLicenses(int index) {
  193. var package = _packages[index];
  194. return _getLicensesForPackage(package);
  195. }
  196. List<LicenseEntry> _getLicensesForPackage(String package) {
  197. var bindings = _packageLicenseBindings[package]!;
  198. return bindings.map((b) => _licenses[b]).toList();
  199. }
  200. }