diff --git a/.gitignore b/.gitignore
index bf2c985e..cc3ff799 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@
.dart_tool/
build/
pubspec.lock
+CLAUDE.md
diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_fab.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_fab.dart
index f0f7a0cd..0eee12d5 100644
--- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_fab.dart
+++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_fab.dart
@@ -70,10 +70,7 @@ class BrowserFab extends HookConsumerWidget {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (child, animation) {
- return ScaleTransition(
- scale: animation,
- child: child,
- );
+ return ScaleTransition(scale: animation, child: child);
},
child: child,
);
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 2802ea5b..9f45f1e9 100644
--- a/app/lib/features/user/domain/presentation/dialogs/select_profile.dart
+++ b/app/lib/features/user/domain/presentation/dialogs/select_profile.dart
@@ -18,12 +18,11 @@
* along with this program. If not, see .
*/
import 'package:flutter/material.dart';
-import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
-import 'package:go_router/go_router.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/utils/profile_switch_handler.dart';
import 'package:weblibre/features/user/domain/repositories/profile.dart';
import 'package:weblibre/presentation/widgets/failure_widget.dart';
import 'package:weblibre/utils/exit_app.dart';
@@ -49,75 +48,7 @@ class SelectProfileDialog extends HookConsumerWidget {
title: Text(profile.name),
subtitle: isSelected ? const Text('Active') : null,
onTap: () async {
- final duplicateMozillaProfile = await filesystem
- .checkForDuplicateMozillaProfile(profile.uuidValue);
-
- if (context.mounted) {
- 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'),
- ),
- ],
- );
- },
- ),
- );
-
- if (result?.$1 == true) {
- if (duplicateMozillaProfile != null && result?.$2 == true) {
- await filesystem.clearMozillaProfileCache(
- duplicateMozillaProfile,
- );
- }
-
- await ref
- .read(profileRepositoryProvider.notifier)
- .switchProfile(profile.id);
- await exitApp(ref.container);
- }
- }
+ await handleSwitchProfile(context, ref, profile);
},
);
}).toList(),
diff --git a/app/lib/features/user/domain/presentation/screens/profile_edit.dart b/app/lib/features/user/domain/presentation/screens/profile_edit.dart
index 396f4e55..d3613f17 100644
--- a/app/lib/features/user/domain/presentation/screens/profile_edit.dart
+++ b/app/lib/features/user/domain/presentation/screens/profile_edit.dart
@@ -24,8 +24,10 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:weblibre/core/filesystem.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/domain/entities/profile.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/utils/form_validators.dart';
@@ -101,6 +103,18 @@ class ProfileEditScreen extends HookConsumerWidget {
},
),
),
+ const SizedBox(height: 16),
+ if (filesystem.selectedProfile != profile!.uuidValue)
+ SizedBox(
+ width: double.infinity,
+ child: OutlinedButton.icon(
+ label: const Text('Switch to this Profile'),
+ icon: const Icon(MdiIcons.accountSwitch),
+ onPressed: () async {
+ await handleSwitchProfile(context, ref, profile!);
+ },
+ ),
+ ),
],
const SizedBox(height: 16),
SizedBox(
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
new file mode 100644
index 00000000..8e92ffcd
--- /dev/null
+++ b/app/lib/features/user/domain/presentation/utils/profile_switch_handler.dart
@@ -0,0 +1,118 @@
+/*
+ * 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';
+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/repositories/profile.dart';
+import 'package:weblibre/utils/exit_app.dart';
+
+/// Handles the profile switching flow with confirmation dialog and cache clearing options.
+///
+/// This function:
+/// - Checks if the profile is already active
+/// - Shows a confirmation dialog with browser restart warning
+/// - Optionally clears shared cache for duplicate Mozilla profiles
+/// - Switches to the selected profile and exits the app
+Future handleSwitchProfile(
+ BuildContext context,
+ WidgetRef ref,
+ Profile profile,
+) async {
+ final isSelected = filesystem.selectedProfile == profile.uuidValue;
+
+ // Don't allow switching to the already active profile
+ if (isSelected) {
+ if (context.mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('This profile is already active')),
+ );
+ }
+ return;
+ }
+
+ final duplicateMozillaProfile = await filesystem
+ .checkForDuplicateMozillaProfile(profile.uuidValue);
+
+ 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'),
+ ),
+ ],
+ );
+ },
+ ),
+ );
+
+ if (result?.$1 == true) {
+ if (duplicateMozillaProfile != null && result?.$2 == true) {
+ await filesystem.clearMozillaProfileCache(duplicateMozillaProfile);
+ }
+
+ await ref
+ .read(profileRepositoryProvider.notifier)
+ .switchProfile(profile.id);
+ await exitApp(ref.container);
+ }
+}