import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:plezy/widgets/app_icon.dart'; import 'package:material_symbols_icons/symbols.dart'; import '../../widgets/focused_scroll_scaffold.dart'; import '../../widgets/loading_indicator_box.dart'; import '../../i18n/strings.g.dart'; class MergedLicenseEntry { final String packageName; final List licenseEntries; final Set allPackageNames; MergedLicenseEntry({required this.packageName, required this.licenseEntries, required this.allPackageNames}); } class LicensesScreen extends StatelessWidget { const LicensesScreen({super.key}); static final Future> _licensesFuture = _loadLicenses(); static Future> _loadLicenses() async { final licenseMap = >{}; final allPackageNames = >{}; await for (final license in LicenseRegistry.licenses) { for (final packageName in license.packages) { if (!licenseMap.containsKey(packageName)) { licenseMap[packageName] = []; allPackageNames[packageName] = {}; } licenseMap[packageName]!.add(license); allPackageNames[packageName]!.addAll(license.packages); } } final mergedLicenses = licenseMap.entries.map((entry) { return MergedLicenseEntry( packageName: entry.key, licenseEntries: entry.value, allPackageNames: allPackageNames[entry.key]!, ); }).toList(); mergedLicenses.sort((a, b) => a.packageName.compareTo(b.packageName)); return mergedLicenses; } @override Widget build(BuildContext context) { return FutureBuilder>( future: _licensesFuture, builder: (context, snapshot) { final mergedLicenses = snapshot.data; if (mergedLicenses == null) { return FocusedScrollScaffold(title: Text(t.screens.licenses), slivers: [LoadingIndicatorBox.sliver]); } return FocusedScrollScaffold( title: Text(t.screens.licenses), slivers: [ SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildBuilderDelegate((context, index) { final mergedLicense = mergedLicenses[index]; final packageName = mergedLicense.packageName; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( title: Text( packageName, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: .bold), ), subtitle: mergedLicense.licenseEntries.length > 1 ? Text(t.licenses.licensesCount(count: mergedLicense.licenseEntries.length)) : null, trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1), onTap: () => _showLicenseDetail(context, mergedLicense), ), ); }, childCount: mergedLicenses.length), ), ), ], ); }, ); } void _showLicenseDetail(BuildContext context, MergedLicenseEntry mergedLicense) { Navigator.push( context, MaterialPageRoute(builder: (context) => _LicenseDetailScreen(mergedLicense: mergedLicense)), ); } } class _LicenseDetailScreen extends StatelessWidget { final MergedLicenseEntry mergedLicense; const _LicenseDetailScreen({required this.mergedLicense}); @override Widget build(BuildContext context) { final packageName = mergedLicense.packageName; final licenseEntries = mergedLicense.licenseEntries; return FocusedScrollScaffold( title: Text(packageName), slivers: [ SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildListDelegate([ if (mergedLicense.allPackageNames.length > 1) Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: .start, children: [ Text( t.licenses.relatedPackages, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: .bold), ), const SizedBox(height: 8), Text(mergedLicense.allPackageNames.join(', '), style: Theme.of(context).textTheme.bodyMedium), ], ), ), ), if (mergedLicense.allPackageNames.length > 1) const SizedBox(height: 16), ...licenseEntries.asMap().entries.map((entry) { final index = entry.key; final license = entry.value; final isMultipleLicenses = licenseEntries.length > 1; return Column( children: [ Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: .start, children: [ Text( isMultipleLicenses ? t.licenses.licenseNumber(number: index + 1) : t.licenses.license, style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: .bold), ), const SizedBox(height: 16), ...license.paragraphs.map((paragraph) { return Padding( padding: const EdgeInsets.only(bottom: 16), child: SelectableText( paragraph.text, style: TextStyle(fontFamily: paragraph.indent > 0 ? 'monospace' : null, fontSize: 14), ), ); }), ], ), ), ), if (index < licenseEntries.length - 1) const SizedBox(height: 16), ], ); }), ]), ), ), ], ); } }