import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/constants.dart'; import '../../providers/auth_provider.dart'; import '../../providers/settings_provider.dart'; import '../../providers/update_provider.dart'; class SettingsScreen extends ConsumerWidget { const SettingsScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final serverUrl = ref.watch(serverUrlProvider); final themeMode = ref.watch(themeModeProvider); final repoUrl = ref.watch(forgejoRepoUrlProvider); final update = ref.watch(updateProvider); return Scaffold( appBar: AppBar(title: const Text('Settings')), body: ListView( children: [ ListTile( title: const Text('Server URL'), subtitle: Text(serverUrl ?? 'Not configured'), leading: const Icon(Icons.dns), onTap: () => context.go(Routes.setup), ), const Divider(), ListTile( title: const Text('Appearance'), leading: const Icon(Icons.brightness_6), trailing: SegmentedButton( segments: const [ ButtonSegment( value: ThemeMode.system, icon: Icon(Icons.brightness_auto), tooltip: 'System', ), ButtonSegment( value: ThemeMode.light, icon: Icon(Icons.light_mode), tooltip: 'Light', ), ButtonSegment( value: ThemeMode.dark, icon: Icon(Icons.dark_mode), tooltip: 'Dark', ), ], selected: {themeMode}, onSelectionChanged: (modes) => ref.read(themeModeProvider.notifier).setMode(modes.first), ), ), const Divider(), // ── Updates ────────────────────────────────────────────────────── ListTile( leading: const Icon(Icons.update), title: const Text('Update repository'), subtitle: Text(repoUrl?.isNotEmpty == true ? repoUrl! : 'Not configured — tap to set'), onTap: () => _editRepoUrl(context, ref, repoUrl), ), ListTile( leading: const Icon(Icons.info_outline), title: const Text('App version'), subtitle: _versionSubtitle(update), trailing: update.status == UpdateStatus.checking ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : TextButton( onPressed: () { final url = ref.read(forgejoRepoUrlProvider); if (url == null || url.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Set a repository URL first.'), ), ); return; } ref.read(updateProvider.notifier).check(url); }, child: const Text('Check'), ), ), if (update.status == UpdateStatus.available || update.status == UpdateStatus.downloading) _UpdateTile(update: update), if (update.status == UpdateStatus.error) ListTile( leading: const Icon(Icons.error_outline, color: Colors.red), title: const Text('Update check failed'), subtitle: Text( update.errorMessage ?? 'Unknown error', maxLines: 2, overflow: TextOverflow.ellipsis, ), ), const Divider(), ListTile( title: const Text('Sign Out'), leading: const Icon(Icons.logout), onTap: () async { await ref.read(authProvider.notifier).logout(); if (context.mounted) context.go(Routes.login); }, ), ], ), ); } Widget _versionSubtitle(UpdateState update) { final current = update.currentVersion; final latest = update.latestVersion; if (current == null && latest == null) { return const Text('Tap "Check" to look for updates'); } if (update.status == UpdateStatus.upToDate) { return Text('v$current — up to date'); } if (update.status == UpdateStatus.available || update.status == UpdateStatus.downloading) { return Text('v$current installed'); } return Text(current != null ? 'v$current' : ''); } Future _editRepoUrl( BuildContext context, WidgetRef ref, String? current) async { String draft = current ?? ''; await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Update repository URL'), content: TextFormField( initialValue: draft, onChanged: (v) => draft = v, decoration: const InputDecoration( hintText: 'https://git.example.com/user/fabled_app', border: OutlineInputBorder(), ), autocorrect: false, keyboardType: TextInputType.url, ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), child: const Text('Cancel'), ), FilledButton( onPressed: () { ref .read(forgejoRepoUrlProvider.notifier) .setUrl(draft.trim()); Navigator.pop(dialogContext); }, child: const Text('Save'), ), ], ), ); } } class _UpdateTile extends ConsumerWidget { final UpdateState update; const _UpdateTile({required this.update}); @override Widget build(BuildContext context, WidgetRef ref) { final isDownloading = update.status == UpdateStatus.downloading; return ListTile( leading: const Icon(Icons.system_update, color: Colors.green), title: Text('v${update.latestVersion} available'), subtitle: isDownloading ? Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 4), LinearProgressIndicator( value: update.downloadProgress > 0 ? update.downloadProgress : null, ), const SizedBox(height: 2), Text( '${(update.downloadProgress * 100).toStringAsFixed(0)}%'), ], ) : const Text('Tap to download and install'), trailing: isDownloading ? null : FilledButton( onPressed: () => ref.read(updateProvider.notifier).downloadAndInstall(), child: const Text('Install'), ), ); } }