refactor: dialog boxes
This commit is contained in:
@@ -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<ChatsDrawer> {
|
||||
}
|
||||
|
||||
Future<void> _promptCreateFolder() async {
|
||||
final theme = context.conduitTheme;
|
||||
final controller = TextEditingController();
|
||||
final name = await showDialog<String>(
|
||||
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<ChatsDrawer> {
|
||||
String folderId,
|
||||
String currentName,
|
||||
) async {
|
||||
final theme = context.conduitTheme;
|
||||
final controller = TextEditingController(text: currentName);
|
||||
|
||||
final newName = await showDialog<String>(
|
||||
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<ChatsDrawer> {
|
||||
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<ChatsDrawer> {
|
||||
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<ChatsDrawer> {
|
||||
String conversationId,
|
||||
String currentTitle,
|
||||
) async {
|
||||
final theme = context.conduitTheme;
|
||||
final controller = TextEditingController(text: currentTitle);
|
||||
|
||||
final newName = await showDialog<String>(
|
||||
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<ChatsDrawer> {
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -115,54 +115,7 @@ class UiUtils {
|
||||
);
|
||||
}
|
||||
|
||||
/// Shows a Conduit-styled confirmation dialog
|
||||
static Future<bool> showConfirmationDialog(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String message,
|
||||
String confirmText = 'Confirm',
|
||||
String cancelText = 'Cancel',
|
||||
bool isDestructive = false,
|
||||
}) async {
|
||||
return await showDialog<bool>(
|
||||
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) {
|
||||
|
||||
@@ -96,4 +96,108 @@ class ThemedDialogs {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Cohesive text input dialog used for rename/create flows
|
||||
static Future<String?> 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<String>(
|
||||
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<TextEditingValue>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user