diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/clear_site_data_dialog.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/clear_site_data_dialog.dart
new file mode 100644
index 00000000..6ab7d5dd
--- /dev/null
+++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/clear_site_data_dialog.dart
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2024-2025 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:flutter/material.dart';
+
+/// Shows a confirmation dialog before clearing site data.
+///
+/// Returns true if the user confirms, false otherwise.
+Future showClearSiteDataDialog(
+ BuildContext context, {
+ required String host,
+ required String formattedTypes,
+}) {
+ return showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ icon: const Icon(Icons.warning),
+ title: const Text('Clear Site Data'),
+ content: Text(
+ 'This will clear $formattedTypes for $host.\n\n'
+ 'You may need to log in again.',
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context, false),
+ child: const Text('Cancel'),
+ ),
+ FilledButton(
+ onPressed: () => Navigator.pop(context, true),
+ child: const Text('Clear'),
+ ),
+ ],
+ ),
+ );
+}
diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/clear_site_data_section.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/clear_site_data_section.dart
index 8b030c36..f4614742 100644
--- a/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/clear_site_data_section.dart
+++ b/app/lib/features/geckoview/features/browser/presentation/widgets/sheets/clear_site_data_section.dart
@@ -22,6 +22,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
+import 'package:weblibre/features/geckoview/features/browser/presentation/dialogs/clear_site_data_dialog.dart';
import 'package:weblibre/utils/ui_helper.dart';
/// Section widget for clearing site data
@@ -140,26 +141,10 @@ class ClearSiteDataSection extends HookConsumerWidget {
return;
}
- final confirmed = await showDialog(
- context: context,
- builder: (context) => AlertDialog(
- icon: const Icon(Icons.warning),
- title: const Text('Clear Site Data'),
- content: Text(
- 'This will clear ${_formatTypes(selectedTypes.value)} for ${url.host}.\n\n'
- 'You may need to log in again.',
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context, false),
- child: const Text('Cancel'),
- ),
- FilledButton(
- onPressed: () => Navigator.pop(context, true),
- child: const Text('Clear'),
- ),
- ],
- ),
+ final confirmed = await showClearSiteDataDialog(
+ context,
+ host: url.host,
+ formattedTypes: _formatTypes(selectedTypes.value),
);
if (confirmed == true && context.mounted) {
diff --git a/app/lib/features/geckoview/features/search/presentation/dialogs/reset_bang_dialog.dart b/app/lib/features/geckoview/features/search/presentation/dialogs/reset_bang_dialog.dart
new file mode 100644
index 00000000..84537b2d
--- /dev/null
+++ b/app/lib/features/geckoview/features/search/presentation/dialogs/reset_bang_dialog.dart
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2024-2025 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:flutter/material.dart';
+
+/// Shows a confirmation dialog for resetting a bang's usage frequency.
+///
+/// Returns true if the user confirms reset, false otherwise.
+Future showResetBangDialog(
+ BuildContext context, {
+ required String triggerName,
+}) {
+ return showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ title: Text('Reset usage frequency of $triggerName?'),
+ content: const Text('This will remove the Bang from quick select.'),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context, false),
+ child: const Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: () => Navigator.pop(context, true),
+ child: const Text('Reset'),
+ ),
+ ],
+ ),
+ );
+}
diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/bang_chips.dart b/app/lib/features/geckoview/features/search/presentation/widgets/bang_chips.dart
index f2f8a30a..bc947fd4 100644
--- a/app/lib/features/geckoview/features/search/presentation/widgets/bang_chips.dart
+++ b/app/lib/features/geckoview/features/search/presentation/widgets/bang_chips.dart
@@ -47,29 +47,6 @@ class BangChips extends HookConsumerWidget {
super.key,
});
- static Future resetBangDialog(
- BuildContext context,
- String triggerName,
- ) {
- return showDialog(
- context: context,
- builder: (context) => AlertDialog(
- title: Text('Reset usage frequency of $triggerName?'),
- content: const Text('This will remove the Bang from quick select.'),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context, false),
- child: const Text('Cancel'),
- ),
- TextButton(
- onPressed: () => Navigator.pop(context, true),
- child: const Text('Reset'),
- ),
- ],
- ),
- );
- }
-
@override
Widget build(BuildContext context, WidgetRef ref) {
final availableBangs = ref.watch(seamlessBangProvider);
diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/full_search_suggestions.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/full_search_suggestions.dart
index e83936c5..aab17cc7 100644
--- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/full_search_suggestions.dart
+++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/full_search_suggestions.dart
@@ -29,6 +29,7 @@ import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
import 'package:weblibre/features/bangs/domain/repositories/data.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/search/domain/providers/search_suggestions.dart';
+import 'package:weblibre/features/geckoview/features/search/presentation/dialogs/reset_bang_dialog.dart';
import 'package:weblibre/features/geckoview/features/search/presentation/widgets/bang_chips.dart';
class FullSearchTermSuggestions extends HookConsumerWidget {
@@ -168,9 +169,9 @@ class FullSearchTermSuggestions extends HookConsumerWidget {
.read(selectedBangTriggerProvider().notifier)
.clearTrigger();
} else {
- final dialogResult = await BangChips.resetBangDialog(
+ final dialogResult = await showResetBangDialog(
context,
- bang.trigger,
+ triggerName: bang.trigger,
);
if (dialogResult == true) {
diff --git a/app/lib/features/settings/presentation/dialogs/delete_all_exceptions_dialog.dart b/app/lib/features/settings/presentation/dialogs/delete_all_exceptions_dialog.dart
new file mode 100644
index 00000000..630adff5
--- /dev/null
+++ b/app/lib/features/settings/presentation/dialogs/delete_all_exceptions_dialog.dart
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2024-2025 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:flutter/material.dart';
+
+/// Shows a confirmation dialog for deleting all tracking protection exceptions.
+///
+/// Returns true if the user confirms deletion, false otherwise.
+Future showDeleteAllExceptionsDialog(BuildContext context) {
+ return showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ title: const Text('Delete All Exceptions?'),
+ content: const Text(
+ 'This will re-enable tracking protection for all exception sites.',
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context, false),
+ child: const Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: () => Navigator.pop(context, true),
+ child: const Text('Delete'),
+ ),
+ ],
+ ),
+ );
+}
diff --git a/app/lib/features/settings/presentation/screens/tracking_protection_exceptions.dart b/app/lib/features/settings/presentation/screens/tracking_protection_exceptions.dart
index a585fe1d..f321fc99 100644
--- a/app/lib/features/settings/presentation/screens/tracking_protection_exceptions.dart
+++ b/app/lib/features/settings/presentation/screens/tracking_protection_exceptions.dart
@@ -23,6 +23,7 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/features/geckoview/features/browser/domain/repositories/tracking_protection.dart';
+import 'package:weblibre/features/settings/presentation/dialogs/delete_all_exceptions_dialog.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart';
import 'package:weblibre/utils/ui_helper.dart';
@@ -91,36 +92,19 @@ class TrackingProtectionExceptionsScreen extends HookConsumerWidget {
}
Future _showDeleteAllDialog(BuildContext context, WidgetRef ref) async {
- await showDialog(
- context: context,
- builder: (context) => AlertDialog(
- title: const Text('Delete All Exceptions?'),
- content: const Text(
- 'This will re-enable tracking protection for all exception sites.',
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: const Text('Cancel'),
- ),
- TextButton(
- onPressed: () async {
- Navigator.pop(context);
- try {
- await ref
- .read(trackingProtectionRepositoryProvider.notifier)
- .removeAllExceptions();
- } catch (e) {
- if (context.mounted) {
- showErrorMessage(context, 'Failed to delete exceptions: $e');
- }
- }
- },
- child: const Text('Delete'),
- ),
- ],
- ),
- );
+ final confirmed = await showDeleteAllExceptionsDialog(context);
+
+ if (confirmed == true) {
+ try {
+ await ref
+ .read(trackingProtectionRepositoryProvider.notifier)
+ .removeAllExceptions();
+ } catch (e) {
+ if (context.mounted) {
+ showErrorMessage(context, 'Failed to delete exceptions: $e');
+ }
+ }
+ }
}
Future _deleteException(
diff --git a/app/lib/features/user/domain/presentation/dialogs/quit_browser_dialog.dart b/app/lib/features/user/domain/presentation/dialogs/quit_browser_dialog.dart
new file mode 100644
index 00000000..131282cc
--- /dev/null
+++ b/app/lib/features/user/domain/presentation/dialogs/quit_browser_dialog.dart
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2024-2025 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:flutter/material.dart';
+
+/// Shows a confirmation dialog for quitting the browser.
+///
+/// Returns true if the user confirms, false if cancelled, null if dismissed.
+Future showQuitBrowserDialog(BuildContext context) {
+ return showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ icon: const Icon(Icons.warning),
+ title: const Text('Quit Browser'),
+ content: const Text(
+ 'This will properly shutdown the browser and clear private tabs',
+ ),
+ actions: [
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context, false);
+ },
+ child: const Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context, true);
+ },
+ child: const Text('Quit'),
+ ),
+ ],
+ );
+ },
+ );
+}
diff --git a/app/lib/features/user/domain/presentation/dialogs/select_profile.dart b/app/lib/features/user/domain/presentation/dialogs/select_profile.dart
index 21c71f3a..529fe3d1 100644
--- a/app/lib/features/user/domain/presentation/dialogs/select_profile.dart
+++ b/app/lib/features/user/domain/presentation/dialogs/select_profile.dart
@@ -22,6 +22,7 @@ import 'package:flutter_material_design_icons/flutter_material_design_icons.dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/core/routing/routes.dart';
+import 'package:weblibre/features/user/domain/presentation/dialogs/quit_browser_dialog.dart';
import 'package:weblibre/features/user/domain/presentation/utils/profile_switch_handler.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart';
@@ -70,32 +71,7 @@ class SelectProfileDialog extends HookConsumerWidget {
iconAlignment: IconAlignment.start,
label: const Text('Quit Browser'),
onPressed: () async {
- final result = await showDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog(
- icon: const Icon(Icons.warning),
- title: const Text('Quit Browser'),
- content: const Text(
- 'This will properly shutdown the browser and clear private tabs',
- ),
- actions: [
- TextButton(
- onPressed: () {
- Navigator.pop(context, false);
- },
- child: const Text('Cancel'),
- ),
- TextButton(
- onPressed: () {
- Navigator.pop(context, true);
- },
- child: const Text('Quit'),
- ),
- ],
- );
- },
- );
+ final result = await showQuitBrowserDialog(context);
if (result == true) {
await exitApp(ref.container);
diff --git a/app/lib/features/user/domain/presentation/dialogs/switch_profile_dialog.dart b/app/lib/features/user/domain/presentation/dialogs/switch_profile_dialog.dart
new file mode 100644
index 00000000..88845b1c
--- /dev/null
+++ b/app/lib/features/user/domain/presentation/dialogs/switch_profile_dialog.dart
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2024-2025 Fabian Freund.
+ *
+ * This file is part of WebLibre
+ * (see https://weblibre.eu).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:go_router/go_router.dart';
+
+/// Result of the switch profile dialog.
+/// - `shouldSwitch`: Whether the user confirmed the switch.
+/// - `clearCache`: Whether to clear the shared cache.
+typedef SwitchProfileDialogResult = ({bool shouldSwitch, bool clearCache});
+
+/// Shows a confirmation dialog for switching user profiles.
+///
+/// Returns a [SwitchProfileDialogResult] if the user confirms, or null if dismissed.
+/// If [duplicateMozillaProfile] is provided, shows an option to clear shared cache.
+Future showSwitchProfileDialog(
+ BuildContext context, {
+ required String profileName,
+ String? duplicateMozillaProfile,
+}) {
+ return showDialog(
+ context: context,
+ builder: (context) => HookBuilder(
+ builder: (context) {
+ final clearCache = useState(false);
+
+ return AlertDialog(
+ icon: const Icon(Icons.warning),
+ title: const Text('Switch User'),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ "Switching to User '$profileName' will require a restart of the Browser.",
+ style: const TextStyle(fontWeight: FontWeight.bold),
+ ),
+ const SizedBox(height: 8),
+ if (duplicateMozillaProfile != null)
+ SwitchListTile(
+ contentPadding: EdgeInsets.zero,
+ value: clearCache.value,
+ title: const Text('Clear Shared Cache'),
+ subtitle: const Text(
+ 'This User has been created based on an exisiting Mozilla Profile Identifier. Clearing cache will affect all linked accounts.',
+ ),
+ onChanged: (value) {
+ clearCache.value = value;
+ },
+ ),
+ ],
+ ),
+ actions: [
+ TextButton(
+ onPressed: () {
+ context.pop((shouldSwitch: false, clearCache: false));
+ },
+ child: const Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: () {
+ context.pop((shouldSwitch: true, clearCache: clearCache.value));
+ },
+ child: const Text('Switch Profile'),
+ ),
+ ],
+ );
+ },
+ ),
+ );
+}
diff --git a/app/lib/features/user/domain/presentation/utils/profile_switch_handler.dart b/app/lib/features/user/domain/presentation/utils/profile_switch_handler.dart
index 9237a667..0b42db61 100644
--- a/app/lib/features/user/domain/presentation/utils/profile_switch_handler.dart
+++ b/app/lib/features/user/domain/presentation/utils/profile_switch_handler.dart
@@ -18,11 +18,10 @@
* along with this program. If not, see .
*/
import 'package:flutter/material.dart';
-import 'package:flutter_hooks/flutter_hooks.dart';
-import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/domain/entities/profile.dart';
+import 'package:weblibre/features/user/domain/presentation/dialogs/switch_profile_dialog.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/utils/exit_app.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
@@ -54,58 +53,14 @@ Future handleSwitchProfile(
if (!context.mounted) return;
- final result = await showDialog<(bool, bool)>(
- context: context,
- builder: (context) => HookBuilder(
- builder: (context) {
- final clearCache = useState(false);
-
- return AlertDialog(
- icon: const Icon(Icons.warning),
- title: const Text('Switch User'),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(
- "Switching to User '${profile.name}' will require a restart of the Browser.",
- style: const TextStyle(fontWeight: FontWeight.bold),
- ),
- const SizedBox(height: 8),
- if (duplicateMozillaProfile != null)
- SwitchListTile(
- contentPadding: EdgeInsets.zero,
- value: clearCache.value,
- title: const Text('Clear Shared Cache'),
- subtitle: const Text(
- 'This User has been created based on an exisiting Mozilla Profile Identifier. Clearing cache will affect all linked accounts.',
- ),
- onChanged: (value) {
- clearCache.value = value;
- },
- ),
- ],
- ),
- actions: [
- TextButton(
- onPressed: () {
- context.pop((false, false));
- },
- child: const Text('Cancel'),
- ),
- TextButton(
- onPressed: () {
- context.pop((true, clearCache.value));
- },
- child: const Text('Switch Profile'),
- ),
- ],
- );
- },
- ),
+ final result = await showSwitchProfileDialog(
+ context,
+ profileName: profile.name,
+ duplicateMozillaProfile: duplicateMozillaProfile,
);
- if (result?.$1 == true) {
- if (duplicateMozillaProfile != null && result?.$2 == true) {
+ if (result?.shouldSwitch == true) {
+ if (duplicateMozillaProfile != null && result?.clearCache == true) {
await filesystem.clearMozillaProfileCache(duplicateMozillaProfile);
}