diff --git a/app/lib/features/chat_archive/presentation/screens/list.dart b/app/lib/features/chat_archive/presentation/screens/list.dart index 4fc844d8..5291dfea 100644 --- a/app/lib/features/chat_archive/presentation/screens/list.dart +++ b/app/lib/features/chat_archive/presentation/screens/list.dart @@ -25,44 +25,41 @@ class ChatArchiveListScreen extends HookConsumerWidget { ), ], ), - body: SafeArea( - child: Skeletonizer( - enabled: chatsAsync.isLoading, - child: chatsAsync.when( - data: (chats) { - return ListView.builder( - itemCount: chats.length, - itemBuilder: (context, index) { - final chat = chats[index]; + body: Skeletonizer( + enabled: chatsAsync.isLoading, + child: chatsAsync.when( + data: (chats) { + return ListView.builder( + itemCount: chats.length, + itemBuilder: (context, index) { + final chat = chats[index]; - return ListTile( - title: Text(chat.toString()), - subtitle: (chat.dateTime != null) - ? Text(chat.dateTime!.formatWithMinutePrecision()) - : null, - onTap: () async { - await context.push( - ChatArchiveDetailRoute(fileName: chat.fileName) - .location, - ); - }, - ); - }, - ); - }, - error: (error, stackTrace) { - return FailureWidget( - title: 'Could not load archived chats', - exception: error, - onRetry: () => ref.refresh(chatArchiveRepositoryProvider), - ); - }, - loading: () => ListView.builder( - itemCount: 3, - itemBuilder: (context, index) => const ListTile( - title: Bone.text(), - subtitle: Bone.text(), - ), + return ListTile( + title: Text(chat.toString()), + subtitle: (chat.dateTime != null) + ? Text(chat.dateTime!.formatWithMinutePrecision()) + : null, + onTap: () async { + await context.push( + ChatArchiveDetailRoute(fileName: chat.fileName).location, + ); + }, + ); + }, + ); + }, + error: (error, stackTrace) { + return FailureWidget( + title: 'Could not load archived chats', + exception: error, + onRetry: () => ref.refresh(chatArchiveRepositoryProvider), + ); + }, + loading: () => ListView.builder( + itemCount: 3, + itemBuilder: (context, index) => const ListTile( + title: Bone.text(), + subtitle: Bone.text(), ), ), ), diff --git a/app/lib/features/settings/presentation/screens/settings.dart b/app/lib/features/settings/presentation/screens/settings.dart index 1f79e334..fc39fca1 100644 --- a/app/lib/features/settings/presentation/screens/settings.dart +++ b/app/lib/features/settings/presentation/screens/settings.dart @@ -90,332 +90,327 @@ class SettingsScreen extends HookConsumerWidget { return Scaffold( appBar: AppBar(title: const Text('Settings')), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - children: [ - _buildSection(theme, 'Kagi'), - Padding( - padding: const EdgeInsets.only( - left: 16, - right: 16, - bottom: 8, - ), - child: TextField( - enableIMEPersonalizedLearning: false, - autocorrect: false, - controller: kagiSessionTextController, - obscureText: hideSessionText.value, - decoration: InputDecoration( - label: const Text('Kagi Session Token'), - hintText: 'https://kagi.com/search?token=...', - helperMaxLines: 2, - helper: Markdown( - shrinkWrap: true, - padding: EdgeInsets.zero, - data: - 'You can visit your [Kagi Account Settings](user_details) to get your Session Link.', - onTapLink: (text, href, title) async { - if (href == 'user_details') { - await ui_helper.launchUrlFeedback( - context, - Uri.parse( - 'https://kagi.com/settings?p=user_details', - ), - ); - } - }, - ), - suffixIcon: IconButton( - onPressed: () { - hideSessionText.value = !hideSessionText.value; - }, - icon: Icon( - hideSessionText.value - ? Icons.visibility - : Icons.visibility_off, - ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + children: [ + _buildSection(theme, 'Kagi'), + Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + bottom: 8, + ), + child: TextField( + enableIMEPersonalizedLearning: false, + autocorrect: false, + controller: kagiSessionTextController, + obscureText: hideSessionText.value, + decoration: InputDecoration( + label: const Text('Kagi Session Token'), + hintText: 'https://kagi.com/search?token=...', + helperMaxLines: 2, + helper: Markdown( + shrinkWrap: true, + padding: EdgeInsets.zero, + data: + 'You can visit your [Kagi Account Settings](user_details) to get your Session Link.', + onTapLink: (text, href, title) async { + if (href == 'user_details') { + await ui_helper.launchUrlFeedback( + context, + Uri.parse( + 'https://kagi.com/settings?p=user_details', + ), + ); + } + }, + ), + suffixIcon: IconButton( + onPressed: () { + hideSessionText.value = !hideSessionText.value; + }, + icon: Icon( + hideSessionText.value + ? Icons.visibility + : Icons.visibility_off, ), ), ), ), - SwitchListTile.adaptive( - title: const Text('Show Early Access Features'), - subtitle: const Text( - "Displays Kagi's early access features in the UI. As an Ultimate subscriber, you will likely want to have this enabled.", - ), - value: showEarlyAccessFeatures, - onChanged: (value) async { - await ref.read(saveSettingsControllerProvider.notifier).save( - (currentSettings) => currentSettings.copyWith - .showEarlyAccessFeatures(value), - ); + ), + SwitchListTile.adaptive( + title: const Text('Show Early Access Features'), + subtitle: const Text( + "Displays Kagi's early access features in the UI. As an Ultimate subscriber, you will likely want to have this enabled.", + ), + value: showEarlyAccessFeatures, + onChanged: (value) async { + await ref.read(saveSettingsControllerProvider.notifier).save( + (currentSettings) => currentSettings.copyWith + .showEarlyAccessFeatures(value), + ); + }, + ), + const SizedBox( + height: 16, + ), + _buildSection(theme, 'General'), + SwitchListTile.adaptive( + title: const Text('Incognito Mode'), + subtitle: const Text( + 'Deletes all browsing data upon app restart for enhanced privacy.', + ), + value: incognitoEnabled, + onChanged: (value) async { + await ref.read(saveSettingsControllerProvider.notifier).save( + (currentSettings) => + currentSettings.copyWith.incognitoMode(value), + ); + }, + ), + SwitchListTile.adaptive( + title: const Text('Enable JavaScript'), + subtitle: const Text( + 'While turning off JavaScript boosts security, privacy, and speed, it may cause some sites to not work as intended.', + ), + value: javacsriptEnabled, + onChanged: (value) async { + await ref.read(saveSettingsControllerProvider.notifier).save( + (currentSettings) => + currentSettings.copyWith.enableJavascript(value), + ); + }, + ), + SwitchListTile.adaptive( + title: const Text('Launch Links Externally'), + subtitle: const Text( + 'Opens all links (except for kagi.com) in your default browser.', + ), + value: launchUrlExternal, + onChanged: (value) async { + await ref.read(saveSettingsControllerProvider.notifier).save( + (currentSettings) => + currentSettings.copyWith.launchUrlExternal(value), + ); + }, + ), + const SizedBox( + height: 16, + ), + _buildSection(theme, 'Bangs'), + ButtonListTile( + title: 'Bang Frequencies', + subtitle: 'Tracked usage for Bang recommendations', + button: FilledButton.icon( + onPressed: () async { + await ref + .read(bangDataRepositoryProvider.notifier) + .clearFrequency(); }, + icon: const Icon(Icons.delete), + label: const Text('Clear'), ), - const SizedBox( - height: 16, - ), - _buildSection(theme, 'General'), - SwitchListTile.adaptive( - title: const Text('Incognito Mode'), - subtitle: const Text( - 'Deletes all browsing data upon app restart for enhanced privacy.', - ), - value: incognitoEnabled, - onChanged: (value) async { - await ref.read(saveSettingsControllerProvider.notifier).save( - (currentSettings) => - currentSettings.copyWith.incognitoMode(value), - ); - }, - ), - SwitchListTile.adaptive( - title: const Text('Enable JavaScript'), - subtitle: const Text( - 'While turning off JavaScript boosts security, privacy, and speed, it may cause some sites to not work as intended.', - ), - value: javacsriptEnabled, - onChanged: (value) async { - await ref.read(saveSettingsControllerProvider.notifier).save( - (currentSettings) => - currentSettings.copyWith.enableJavascript(value), - ); - }, - ), - SwitchListTile.adaptive( - title: const Text('Launch Links Externally'), - subtitle: const Text( - 'Opens all links (except for kagi.com) in your default browser.', - ), - value: launchUrlExternal, - onChanged: (value) async { - await ref.read(saveSettingsControllerProvider.notifier).save( - (currentSettings) => - currentSettings.copyWith.launchUrlExternal(value), - ); - }, - ), - const SizedBox( - height: 16, - ), - _buildSection(theme, 'Bangs'), - ButtonListTile( - title: 'Bang Frequencies', - subtitle: 'Tracked usage for Bang recommendations', - 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 size = ref.watch( - bangIconCacheSizeMegabytesProvider.select( - (value) => value.valueOrNull, - ), - ); + ), + Consumer( + builder: (context, ref, child) { + final size = ref.watch( + bangIconCacheSizeMegabytesProvider.select( + (value) => value.valueOrNull, + ), + ); - return ButtonListTile( - title: 'Icon Cache', - subtitle: 'Stored favicons for Bangs', - content: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: DefaultTextStyle( - style: GoogleFonts.robotoMono(), - child: Table( - children: [ - TableRow( - children: [ - const Text('Size'), - Text('${size?.toStringAsFixed(2) ?? 0} MB'), - ], - ), - ], - ), + return ButtonListTile( + title: 'Icon Cache', + subtitle: 'Stored favicons for Bangs', + content: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: DefaultTextStyle( + style: GoogleFonts.robotoMono(), + child: Table( + children: [ + TableRow( + children: [ + const Text('Size'), + Text('${size?.toStringAsFixed(2) ?? 0} MB'), + ], + ), + ], ), ), - button: FilledButton.icon( - onPressed: () async { - await ref - .read(bangDataRepositoryProvider.notifier) - .clearIconData(); - }, - 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, - ), - ); + ), + button: FilledButton.icon( + onPressed: () async { + await ref + .read(bangDataRepositoryProvider.notifier) + .clearIconData(); + }, + 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, - ), - ); + 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', - ), - ], - ), - ], - ), + 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, - ), - ); + ), + 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, - ), - ); + 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', - ), - ], - ), - ], - ), + 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, - ), - ); + ), + 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, - ), - ); + 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', - ), - ], - ), - ], - ), + 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'), - ), - ); - }, - ), - ], - ), + ), + button: FilledButton.icon( + onPressed: () async { + await ref + .read(bangSyncRepositoryProvider.notifier) + .syncKagiBangs(); + }, + icon: const Icon(Icons.sync), + label: const Text('Sync'), + ), + ); + }, + ), + ], ), ), );