Files
plezy/lib/screens/settings/licenses_screen.dart
T
2025-12-12 15:57:22 +01:00

225 lines
7.3 KiB
Dart

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 '../../i18n/strings.g.dart';
class MergedLicenseEntry {
final String packageName;
final List<LicenseEntry> licenseEntries;
final Set<String> allPackageNames;
MergedLicenseEntry({
required this.packageName,
required this.licenseEntries,
required this.allPackageNames,
});
}
class LicensesScreen extends StatefulWidget {
const LicensesScreen({super.key});
@override
State<LicensesScreen> createState() => _LicensesScreenState();
}
class _LicensesScreenState extends State<LicensesScreen> {
List<MergedLicenseEntry> _mergedLicenses = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadLicenses();
}
Future<void> _loadLicenses() async {
final licenseMap = <String, List<LicenseEntry>>{};
final allPackageNames = <String, Set<String>>{};
await for (final license in LicenseRegistry.licenses) {
for (final packageName in license.packages) {
if (!licenseMap.containsKey(packageName)) {
licenseMap[packageName] = [];
allPackageNames[packageName] = <String>{};
}
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));
if (mounted) {
setState(() {
_mergedLicenses = mergedLicenses;
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return FocusedScrollScaffold(
title: Text(t.screens.licenses),
slivers: const [
SliverFillRemaining(
child: Center(child: CircularProgressIndicator()),
),
],
);
}
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: 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(mergedLicense),
),
);
}, childCount: _mergedLicenses.length),
),
),
],
);
}
void _showLicenseDetail(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([
// Package info card
if (mergedLicense.allPackageNames.length > 1)
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t.licenses.relatedPackages,
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: 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),
// License cards
...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: CrossAxisAlignment.start,
children: [
Text(
isMultipleLicenses
? t.licenses.licenseNumber(number: index + 1)
: t.licenses.license,
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: 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),
],
);
}),
]),
),
),
],
);
}
}