From d844a18b557b8e90544aca9b324121c983408ab8 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Mon, 29 Dec 2025 10:45:58 +0100 Subject: [PATCH] add switch button in user edit --- .gitignore | 1 + .../widgets/browser_modules/browser_fab.dart | 5 +- .../presentation/dialogs/select_profile.dart | 73 +---------- .../presentation/screens/profile_edit.dart | 14 +++ .../utils/profile_switch_handler.dart | 118 ++++++++++++++++++ 5 files changed, 136 insertions(+), 75 deletions(-) create mode 100644 app/lib/features/user/domain/presentation/utils/profile_switch_handler.dart 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); + } +}