diff --git a/app/assets/fonts/RobotoMono-Regular.ttf b/app/assets/fonts/RobotoMono-Regular.ttf new file mode 100644 index 00000000..6df2b253 Binary files /dev/null and b/app/assets/fonts/RobotoMono-Regular.ttf differ diff --git a/app/lib/features/settings/presentation/screens/settings.dart b/app/lib/features/settings/presentation/screens/settings.dart index 601aa1c3..0939bc17 100644 --- a/app/lib/features/settings/presentation/screens/settings.dart +++ b/app/lib/features/settings/presentation/screens/settings.dart @@ -1,19 +1,40 @@ +import 'package:bang_navigator/core/extension/date_time.dart'; +import 'package:bang_navigator/features/bangs/data/models/bang.dart'; +import 'package:bang_navigator/features/bangs/domain/providers.dart'; +import 'package:bang_navigator/features/bangs/domain/repositories/bang_data.dart'; +import 'package:bang_navigator/features/bangs/domain/repositories/sync.dart'; import 'package:bang_navigator/features/settings/data/models/settings.dart'; import 'package:bang_navigator/features/settings/data/repositories/settings_repository.dart'; import 'package:bang_navigator/features/settings/presentation/controllers/save_settings.dart'; +import 'package:bang_navigator/features/settings/presentation/widgets/button_list_tile.dart'; import 'package:bang_navigator/features/settings/utils/session_link_extractor.dart'; import 'package:bang_navigator/presentation/hooks/listenable_callback.dart'; import 'package:bang_navigator/utils/ui_helper.dart' as ui_helper; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class SettingsScreen extends HookConsumerWidget { const SettingsScreen({super.key}); + Widget _buildSection(ThemeData theme, String name) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(name, style: theme.textTheme.titleLarge), + const Divider(), + ], + ), + ); + @override Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final kagiSessionTextController = useTextEditingController( text: ref.read( settingsRepositoryProvider @@ -74,11 +95,12 @@ class SettingsScreen extends HookConsumerWidget { padding: const EdgeInsets.all(8.0), child: ListView( children: [ + _buildSection(theme, 'Kagi'), Padding( padding: const EdgeInsets.only( left: 16, right: 16, - bottom: 16, + bottom: 8, ), child: TextField( controller: kagiSessionTextController, @@ -129,6 +151,10 @@ class SettingsScreen extends HookConsumerWidget { ); }, ), + const SizedBox( + height: 16, + ), + _buildSection(theme, 'General'), SwitchListTile.adaptive( title: const Text('Incognito Mode'), subtitle: const Text( @@ -168,6 +194,198 @@ class SettingsScreen extends HookConsumerWidget { ); }, ), + const SizedBox( + height: 16, + ), + _buildSection(theme, 'Bangs'), + ButtonListTile( + title: 'Icon Cache', + subtitle: 'Stores favicons for Bangs', + button: FilledButton.icon( + onPressed: () async { + await ref + .read(bangDataRepositoryProvider.notifier) + .clearIconData(); + }, + icon: const Icon(Icons.delete), + label: const Text('Clear'), + ), + ), + ButtonListTile( + title: 'Bang Frequencies', + subtitle: 'Tracks Bang usage for recommendation history', + button: FilledButton.icon( + onPressed: () async { + await ref + .read(bangDataRepositoryProvider.notifier) + .clearFrequency(); + }, + icon: const Icon(Icons.delete), + label: const Text('Clear'), + ), + ), + Consumer( + builder: (context, ref, child) { + final lastSync = ref.watch( + lastSyncOfGroupProvider(BangGroup.general).select( + (value) => value.valueOrNull, + ), + ); + + final count = ref.watch( + bangCountOfGroupProvider(BangGroup.general).select( + (value) => value.valueOrNull, + ), + ); + + return ButtonListTile( + title: 'General Bangs', + subtitle: 'Automatically syncs every 7 days', + content: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: DefaultTextStyle( + style: GoogleFonts.robotoMono(), + child: Table( + children: [ + TableRow( + children: [ + const Text('Entries'), + Text(count?.toString() ?? 'N/A'), + ], + ), + TableRow( + children: [ + const Text('Last Sync'), + Text( + lastSync?.formatWithMinutePrecision() ?? + 'N/A', + ), + ], + ), + ], + ), + ), + ), + button: FilledButton.icon( + onPressed: () async { + await ref + .read(bangSyncRepositoryProvider.notifier) + .syncGeneralBangs(); + }, + icon: const Icon(Icons.sync), + label: const Text('Sync'), + ), + ); + }, + ), + Consumer( + builder: (context, ref, child) { + final lastSync = ref.watch( + lastSyncOfGroupProvider(BangGroup.assistant).select( + (value) => value.valueOrNull, + ), + ); + + final count = ref.watch( + bangCountOfGroupProvider(BangGroup.assistant).select( + (value) => value.valueOrNull, + ), + ); + + return ButtonListTile( + title: 'Assistant Bangs', + subtitle: 'Automatically syncs every 7 days', + content: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: DefaultTextStyle( + style: GoogleFonts.robotoMono(), + child: Table( + children: [ + TableRow( + children: [ + const Text('Entries'), + Text(count?.toString() ?? 'N/A'), + ], + ), + TableRow( + children: [ + const Text('Last Sync'), + Text( + lastSync?.formatWithMinutePrecision() ?? + 'N/A', + ), + ], + ), + ], + ), + ), + ), + button: FilledButton.icon( + onPressed: () async { + await ref + .read(bangSyncRepositoryProvider.notifier) + .syncAssistantBangs(); + }, + icon: const Icon(Icons.sync), + label: const Text('Sync'), + ), + ); + }, + ), + Consumer( + builder: (context, ref, child) { + final lastSync = ref.watch( + lastSyncOfGroupProvider(BangGroup.kagi).select( + (value) => value.valueOrNull, + ), + ); + + final count = ref.watch( + bangCountOfGroupProvider(BangGroup.kagi).select( + (value) => value.valueOrNull, + ), + ); + + return ButtonListTile( + title: 'Kagi Bangs', + subtitle: 'Automatically syncs every 7 days', + content: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: DefaultTextStyle( + style: GoogleFonts.robotoMono(), + child: Table( + children: [ + TableRow( + children: [ + const Text('Entries'), + Text(count?.toString() ?? 'N/A'), + ], + ), + TableRow( + children: [ + const Text('Last Sync'), + Text( + lastSync?.formatWithMinutePrecision() ?? + 'N/A', + ), + ], + ), + ], + ), + ), + ), + button: FilledButton.icon( + onPressed: () async { + await ref + .read(bangSyncRepositoryProvider.notifier) + .syncKagiBangs(); + }, + icon: const Icon(Icons.sync), + label: const Text('Sync'), + ), + ); + }, + ), ], ), ), diff --git a/app/lib/features/settings/presentation/widgets/button_list_tile.dart b/app/lib/features/settings/presentation/widgets/button_list_tile.dart new file mode 100644 index 00000000..de3f9b99 --- /dev/null +++ b/app/lib/features/settings/presentation/widgets/button_list_tile.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +class ButtonListTile extends StatelessWidget { + final String title; + final String subtitle; + final Widget? content; + + final Widget button; + + const ButtonListTile({ + super.key, + required this.title, + required this.subtitle, + this.content, + required this.button, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), + child: Row( + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: theme.textTheme.bodyLarge, + ), + Text( + subtitle, + style: theme.textTheme.bodyMedium, + ), + if (content != null) content!, + ], + ), + ), + button, + ], + ), + ); + } +} diff --git a/app/pubspec.lock b/app/pubspec.lock index 52780848..ee026c65 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -579,6 +579,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 + url: "https://pub.dev" + source: hosted + version: "6.2.1" graphs: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 90b79748..f10065b3 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: flutter_secure_storage: ^9.0.0 flutter_sharing_intent: ^1.1.1 go_router: ^14.1.1 + google_fonts: ^6.2.1 home_widget: ^0.6.0 hooks_riverpod: ^2.5.1 html: ^0.15.4 @@ -70,6 +71,7 @@ flutter: assets: - assets/icon/icon.png - assets/landing/ + - assets/fonts/ flutter_launcher_icons: android: "launcher_icon"