diff --git a/lib/features/navigation/widgets/chats_drawer.dart b/lib/features/navigation/widgets/chats_drawer.dart index aeb37ac..04e2f79 100644 --- a/lib/features/navigation/widgets/chats_drawer.dart +++ b/lib/features/navigation/widgets/chats_drawer.dart @@ -12,6 +12,7 @@ import '../../chat/providers/chat_providers.dart' as chat; // import '../../files/views/files_page.dart'; import '../../profile/views/profile_page.dart'; import '../../../shared/utils/ui_utils.dart'; +import '../../../shared/widgets/themed_dialogs.dart'; import '../../../core/auth/auth_state_manager.dart'; import 'package:conduit/l10n/app_localizations.dart'; import '../../../core/models/user.dart' as models; @@ -580,43 +581,12 @@ class _ChatsDrawerState extends ConsumerState { } Future _promptCreateFolder() async { - final theme = context.conduitTheme; - final controller = TextEditingController(); - final name = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - backgroundColor: theme.surfaceBackground, - title: Text( - AppLocalizations.of(context)!.newFolder, - style: TextStyle(color: theme.textPrimary), - ), - content: TextField( - controller: controller, - autofocus: true, - style: TextStyle(color: theme.inputText), - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.folderName, - hintStyle: TextStyle(color: theme.inputPlaceholder), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: theme.inputBorder), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: theme.buttonPrimary), - ), - ), - onSubmitted: (v) => Navigator.pop(ctx, controller.text.trim()), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: Text(AppLocalizations.of(context)!.cancel), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, controller.text.trim()), - child: Text(AppLocalizations.of(context)!.create), - ), - ], - ), + final name = await ThemedDialogs.promptTextInput( + context, + title: AppLocalizations.of(context)!.newFolder, + hintText: AppLocalizations.of(context)!.folderName, + confirmText: AppLocalizations.of(context)!.create, + cancelText: AppLocalizations.of(context)!.cancel, ); if (name == null) return; @@ -822,50 +792,13 @@ class _ChatsDrawerState extends ConsumerState { String folderId, String currentName, ) async { - final theme = context.conduitTheme; - final controller = TextEditingController(text: currentName); - - final newName = await showDialog( - context: context, - builder: (dialogContext) { - return AlertDialog( - backgroundColor: theme.surfaceBackground, - title: Text( - AppLocalizations.of(context)!.rename, - style: TextStyle(color: theme.textPrimary), - ), - content: TextField( - controller: controller, - autofocus: true, - style: TextStyle(color: theme.inputText), - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.folderName, - hintStyle: TextStyle(color: theme.inputPlaceholder), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: theme.inputBorder), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: theme.buttonPrimary), - ), - ), - textInputAction: TextInputAction.done, - onSubmitted: (value) => Navigator.pop(dialogContext, value.trim()), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(dialogContext), - child: Text(AppLocalizations.of(context)!.cancel), - ), - TextButton( - onPressed: () { - HapticFeedback.lightImpact(); - Navigator.pop(dialogContext, controller.text.trim()); - }, - child: Text(AppLocalizations.of(context)!.save), - ), - ], - ); - }, + final newName = await ThemedDialogs.promptTextInput( + context, + title: AppLocalizations.of(context)!.rename, + hintText: AppLocalizations.of(context)!.folderName, + initialValue: currentName, + confirmText: AppLocalizations.of(context)!.save, + cancelText: AppLocalizations.of(context)!.cancel, ); if (newName == null) return; @@ -892,10 +825,10 @@ class _ChatsDrawerState extends ConsumerState { String folderId, String folderName, ) async { - final confirmed = await UiUtils.showConfirmationDialog( + final confirmed = await ThemedDialogs.confirm( context, - title: 'Delete Folder', - message: 'This folder and its assignment references will be removed.', + title: AppLocalizations.of(context)!.deleteFolderTitle, + message: AppLocalizations.of(context)!.deleteFolderMessage, confirmText: AppLocalizations.of(context)!.delete, isDestructive: true, ); @@ -912,7 +845,7 @@ class _ChatsDrawerState extends ConsumerState { if (!mounted) return; UiUtils.showMessage( this.context, - 'Failed to delete folder', + AppLocalizations.of(context)!.failedToDeleteFolder, isError: true, ); } @@ -1470,50 +1403,13 @@ class _ChatsDrawerState extends ConsumerState { String conversationId, String currentTitle, ) async { - final theme = context.conduitTheme; - final controller = TextEditingController(text: currentTitle); - - final newName = await showDialog( - context: context, - builder: (dialogContext) { - return AlertDialog( - backgroundColor: theme.surfaceBackground, - title: Text( - AppLocalizations.of(context)!.renameChat, - style: TextStyle(color: theme.textPrimary), - ), - content: TextField( - controller: controller, - autofocus: true, - style: TextStyle(color: theme.inputText), - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.enterChatName, - hintStyle: TextStyle(color: theme.inputPlaceholder), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: theme.inputBorder), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: theme.buttonPrimary), - ), - ), - textInputAction: TextInputAction.done, - onSubmitted: (value) => Navigator.pop(dialogContext, value.trim()), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(dialogContext), - child: Text(AppLocalizations.of(context)!.cancel), - ), - TextButton( - onPressed: () { - HapticFeedback.lightImpact(); - Navigator.pop(dialogContext, controller.text.trim()); - }, - child: Text(AppLocalizations.of(context)!.save), - ), - ], - ); - }, + final newName = await ThemedDialogs.promptTextInput( + context, + title: AppLocalizations.of(context)!.renameChat, + hintText: AppLocalizations.of(context)!.enterChatName, + initialValue: currentTitle, + confirmText: AppLocalizations.of(context)!.save, + cancelText: AppLocalizations.of(context)!.cancel, ); if (newName == null) return; @@ -1546,7 +1442,7 @@ class _ChatsDrawerState extends ConsumerState { BuildContext context, String conversationId, ) async { - final confirmed = await UiUtils.showConfirmationDialog( + final confirmed = await ThemedDialogs.confirm( context, title: AppLocalizations.of(context)!.deleteChatTitle, message: AppLocalizations.of(context)!.deleteChatMessage, diff --git a/lib/features/profile/views/profile_page.dart b/lib/features/profile/views/profile_page.dart index 9efe2a6..e9063ad 100644 --- a/lib/features/profile/views/profile_page.dart +++ b/lib/features/profile/views/profile_page.dart @@ -11,6 +11,7 @@ import '../../../core/widgets/error_boundary.dart'; import '../../../shared/widgets/improved_loading_states.dart'; import '../../../shared/utils/ui_utils.dart'; +import '../../../shared/widgets/themed_dialogs.dart'; import '../../../shared/widgets/sheet_handle.dart'; import '../../../shared/widgets/conduit_components.dart'; import '../../../core/providers/app_providers.dart'; @@ -574,7 +575,7 @@ class ProfilePage extends ConsumerWidget { } void _signOut(BuildContext context, WidgetRef ref) async { - final confirm = await UiUtils.showConfirmationDialog( + final confirm = await ThemedDialogs.confirm( context, title: AppLocalizations.of(context)!.signOut, message: AppLocalizations.of(context)!.endYourSession, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 185caa1..73db54e 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -199,6 +199,9 @@ }, "deleteChatTitle": "Chat löschen", "deleteChatMessage": "Dieser Chat wird dauerhaft gelöscht.", + "deleteFolderTitle": "Ordner löschen", + "deleteFolderMessage": "Dieser Ordner und seine Zuordnungen werden entfernt.", + "failedToDeleteFolder": "Ordner konnte nicht gelöscht werden", "aboutApp": "Über die App", "aboutAppSubtitle": "Conduit Informationen und Links", "typeBelowToBegin": "Unten tippen, um zu beginnen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d63128b..93f16fa 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -405,6 +405,12 @@ "@deleteChatTitle": {"description": "Dialog title asking to confirm deletion of a chat."}, "deleteChatMessage": "This chat will be permanently deleted.", "@deleteChatMessage": {"description": "Warning that deleting a chat cannot be undone."}, + "deleteFolderTitle": "Delete Folder", + "@deleteFolderTitle": {"description": "Dialog title asking to confirm deletion of a folder."}, + "deleteFolderMessage": "This folder and its assignment references will be removed.", + "@deleteFolderMessage": {"description": "Warning that deleting a folder will remove it and its associations."}, + "failedToDeleteFolder": "Failed to delete folder", + "@failedToDeleteFolder": {"description": "Error notice when deleting a folder fails."}, "aboutApp": "About App", "@aboutApp": {"description": "Settings tile title to view app information."}, "aboutAppSubtitle": "Conduit information and links", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 03705f1..89cafd8 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -199,7 +199,10 @@ }, "deleteChatTitle": "Supprimer le chat", "deleteChatMessage": "Ce chat sera supprimé définitivement.", - "aboutApp": "À propos de l'application", + "deleteFolderTitle": "Supprimer le dossier", + "deleteFolderMessage": "Ce dossier et ses associations seront supprimés.", + "failedToDeleteFolder": "Échec de la suppression du dossier", + "aboutApp": "À propos de l’application", "aboutAppSubtitle": "Informations et liens Conduit", "typeBelowToBegin": "Saisissez ci‑dessous pour commencer", "web": "Web", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 659eaa8..0792296 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -199,7 +199,10 @@ }, "deleteChatTitle": "Elimina chat", "deleteChatMessage": "Questa chat verrà eliminata definitivamente.", - "aboutApp": "Informazioni sull'app", + "deleteFolderTitle": "Elimina cartella", + "deleteFolderMessage": "Questa cartella e le sue associazioni verranno rimosse.", + "failedToDeleteFolder": "Impossibile eliminare la cartella", + "aboutApp": "Informazioni sull’app", "aboutAppSubtitle": "Informazioni e link di Conduit", "typeBelowToBegin": "Scrivi qui sotto per iniziare", "web": "Web", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index ee48477..5e5f12c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1182,6 +1182,24 @@ abstract class AppLocalizations { /// **'This chat will be permanently deleted.'** String get deleteChatMessage; + /// Dialog title asking to confirm deletion of a folder. + /// + /// In en, this message translates to: + /// **'Delete Folder'** + String get deleteFolderTitle; + + /// Warning that deleting a folder will remove it and its associations. + /// + /// In en, this message translates to: + /// **'This folder and its assignment references will be removed.'** + String get deleteFolderMessage; + + /// Error notice when deleting a folder fails. + /// + /// In en, this message translates to: + /// **'Failed to delete folder'** + String get failedToDeleteFolder; + /// Settings tile title to view app information. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index fcf04b6..935e714 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -600,6 +600,16 @@ class AppLocalizationsDe extends AppLocalizations { @override String get deleteChatMessage => 'Dieser Chat wird dauerhaft gelöscht.'; + @override + String get deleteFolderTitle => 'Ordner löschen'; + + @override + String get deleteFolderMessage => + 'Dieser Ordner und seine Zuordnungen werden entfernt.'; + + @override + String get failedToDeleteFolder => 'Ordner konnte nicht gelöscht werden'; + @override String get aboutApp => 'Über die App'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index c092f81..8042c03 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -595,6 +595,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get deleteChatMessage => 'This chat will be permanently deleted.'; + @override + String get deleteFolderTitle => 'Delete Folder'; + + @override + String get deleteFolderMessage => + 'This folder and its assignment references will be removed.'; + + @override + String get failedToDeleteFolder => 'Failed to delete folder'; + @override String get aboutApp => 'About App'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index ee9dc13..2abaff3 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -605,6 +605,17 @@ class AppLocalizationsFr extends AppLocalizations { @override String get deleteChatMessage => 'Ce chat sera supprimé définitivement.'; + @override + String get deleteFolderTitle => 'Supprimer le dossier'; + + @override + String get deleteFolderMessage => + 'Ce dossier et ses associations seront supprimés.'; + + @override + String get failedToDeleteFolder => + 'Échec de la suppression du dossier'; + @override String get aboutApp => 'À propos de l\'application'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index ef7a782..9f57f4b 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -598,6 +598,17 @@ class AppLocalizationsIt extends AppLocalizations { String get deleteChatMessage => 'Questa chat verrà eliminata definitivamente.'; + @override + String get deleteFolderTitle => 'Elimina cartella'; + + @override + String get deleteFolderMessage => + 'Questa cartella e le sue associazioni verranno rimosse.'; + + @override + String get failedToDeleteFolder => + 'Impossibile eliminare la cartella'; + @override String get aboutApp => 'Informazioni sull\'app'; diff --git a/lib/shared/utils/ui_utils.dart b/lib/shared/utils/ui_utils.dart index e2bd11d..bad5a8b 100644 --- a/lib/shared/utils/ui_utils.dart +++ b/lib/shared/utils/ui_utils.dart @@ -115,54 +115,7 @@ class UiUtils { ); } - /// Shows a Conduit-styled confirmation dialog - static Future showConfirmationDialog( - BuildContext context, { - required String title, - required String message, - String confirmText = 'Confirm', - String cancelText = 'Cancel', - bool isDestructive = false, - }) async { - return await showDialog( - context: context, - builder: (context) => AlertDialog( - backgroundColor: context.conduitTheme.surfaceBackground, - title: Text( - title, - style: TextStyle(color: context.conduitTheme.textPrimary), - ), - content: Text( - message, - style: TextStyle(color: context.conduitTheme.textSecondary), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: Text( - cancelText.isNotEmpty - ? cancelText - : AppLocalizations.of(context)!.cancel, - ), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - style: isDestructive - ? TextButton.styleFrom( - foregroundColor: context.conduitTheme.error, - ) - : null, - child: Text( - confirmText.isNotEmpty - ? confirmText - : AppLocalizations.of(context)!.confirm, - ), - ), - ], - ), - ) ?? - false; - } + // Confirmation dialog moved to shared ThemedDialogs.confirm for cohesion /// Formats dates in a conversational way following Conduit patterns static String formatDate(DateTime date) { diff --git a/lib/shared/widgets/themed_dialogs.dart b/lib/shared/widgets/themed_dialogs.dart index e948175..c7e8c45 100644 --- a/lib/shared/widgets/themed_dialogs.dart +++ b/lib/shared/widgets/themed_dialogs.dart @@ -96,4 +96,108 @@ class ThemedDialogs { ), ); } + + /// Cohesive text input dialog used for rename/create flows + static Future promptTextInput( + BuildContext context, { + required String title, + required String hintText, + String? initialValue, + String confirmText = 'Save', + String cancelText = 'Cancel', + bool barrierDismissible = true, + TextInputType? keyboardType, + TextCapitalization textCapitalization = TextCapitalization.sentences, + int? maxLength, + }) async { + final theme = context.conduitTheme; + final controller = TextEditingController(text: initialValue ?? ''); + + String? result = await showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (ctx) { + return buildBase( + context: ctx, + title: title, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: controller, + autofocus: true, + keyboardType: keyboardType, + textCapitalization: textCapitalization, + maxLength: maxLength, + style: TextStyle(color: theme.inputText), + decoration: InputDecoration( + hintText: hintText, + hintStyle: TextStyle(color: theme.inputPlaceholder), + filled: true, + fillColor: theme.inputBackground, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppBorderRadius.md), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppBorderRadius.md), + borderSide: BorderSide(color: theme.inputBorder, width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(AppBorderRadius.md), + borderSide: + BorderSide(color: theme.buttonPrimary, width: 1), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: Spacing.md, + vertical: Spacing.md, + ), + ), + onSubmitted: (v) { + final trimmed = v.trim(); + final unchanged = (initialValue != null && + trimmed == initialValue.trim()); + if (trimmed.isEmpty || unchanged) return; + Navigator.of(ctx).pop(trimmed); + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: Text( + cancelText, + style: TextStyle(color: theme.textSecondary), + ), + ), + ValueListenableBuilder( + valueListenable: controller, + builder: (context, value, _) { + final trimmed = value.text.trim(); + final unchanged = + (initialValue != null && trimmed == initialValue.trim()); + final enabled = trimmed.isNotEmpty && !unchanged; + return TextButton( + onPressed: enabled + ? () => Navigator.of(ctx).pop(trimmed) + : null, + child: Text( + confirmText, + style: TextStyle( + color: enabled + ? theme.buttonPrimary + : theme.textSecondary, + ), + ), + ); + }, + ), + ], + ); + }, + ); + + return result; + } }