dnf-package-store/lib/settings_page.dart
Philip Peterson 0788b01ff9 Rename to DNF Package Store; add category browsing, settings, and package tags
- Add app/package category browsing via AppStream + comps groups
  (lib/category_service.dart), with per-package tag chips shown when a
  package is selected.
- Add a Settings page to uninstall PackageKit if it's no longer wanted.
- Rename the app from "Package Launcher"/pkg_launcher to "DNF Package
  Store" across the pubspec, window title, and Linux CMake/runner config.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 04:04:22 -08:00

126 lines
4.2 KiB
Dart

import 'package:flutter/material.dart';
import 'category_service.dart';
import 'dnf_service.dart';
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
bool? _packageKitInstalled;
bool _uninstalling = false;
final List<String> _log = [];
final ScrollController _logScroll = ScrollController();
@override
void initState() {
super.initState();
_checkPackageKit();
}
Future<void> _checkPackageKit() async {
final installed = await CategoryService.isFullCatalogAvailable();
if (mounted) setState(() => _packageKitInstalled = installed);
}
Future<void> _confirmAndUninstall() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Uninstall PackageKit?'),
content: const Text(
'This removes PackageKit and disables AppStream-based app categories until it is reinstalled.'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
FilledButton(onPressed: () => Navigator.pop(context, true), child: const Text('Uninstall')),
],
),
);
if (confirmed != true) return;
setState(() {
_uninstalling = true;
_log.clear();
});
try {
final exitCode = await DnfService.uninstall('PackageKit', (line) {
setState(() => _log.add(line));
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_logScroll.hasClients) {
_logScroll.jumpTo(_logScroll.position.maxScrollExtent);
}
});
});
await _checkPackageKit();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(exitCode == 0 ? 'PackageKit removed.' : 'Uninstall failed — see log.'),
));
}
} finally {
if (mounted) setState(() => _uninstalling = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
child: ListTile(
leading: Icon(
_packageKitInstalled == true ? Icons.check_circle : Icons.remove_circle_outline,
color: _packageKitInstalled == true ? Colors.green.shade600 : Colors.grey,
),
title: const Text('PackageKit'),
subtitle: Text(switch (_packageKitInstalled) {
null => 'Checking...',
true => 'Installed — powers AppStream app categories.',
false => 'Not installed.',
}),
trailing: _packageKitInstalled == true
? TextButton.icon(
onPressed: _uninstalling ? null : _confirmAndUninstall,
icon: _uninstalling
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(strokeWidth: 2))
: const Icon(Icons.delete_outline),
label: Text(_uninstalling ? 'Uninstalling...' : 'Uninstall'),
)
: null,
),
),
if (_log.isNotEmpty) ...[
const SizedBox(height: 16),
Expanded(
child: Container(
color: Colors.black87,
padding: const EdgeInsets.all(8),
child: ListView.builder(
controller: _logScroll,
itemCount: _log.length,
itemBuilder: (context, i) => Text(
_log[i],
style:
const TextStyle(color: Colors.white, fontFamily: 'monospace', fontSize: 12),
),
),
),
),
],
],
),
),
);
}
}