Files
iiEsaywebUIapp/lib/shared/widgets/themed_dialogs.dart

215 lines
6.9 KiB
Dart
Raw Permalink Normal View History

2025-08-10 01:20:45 +05:30
import 'package:flutter/material.dart';
import 'package:conduit/l10n/app_localizations.dart';
2025-08-10 01:20:45 +05:30
import '../theme/theme_extensions.dart';
/// Centralized helper for building themed dialogs consistently
class ThemedDialogs {
ThemedDialogs._();
/// Build a base themed AlertDialog
static AlertDialog buildBase({
required BuildContext context,
required String title,
Widget? content,
List<Widget>? actions,
}) {
return AlertDialog(
backgroundColor: context.conduitTheme.surfaceBackground,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.dialog),
),
title: Text(
title,
style: TextStyle(color: context.conduitTheme.textPrimary),
),
content: content,
actions: actions,
);
}
/// Show a simple confirmation dialog with Cancel/Confirm actions
static Future<bool> confirm(
BuildContext context, {
required String title,
required String message,
String? confirmText,
String? cancelText,
2025-08-10 01:20:45 +05:30
bool isDestructive = false,
bool barrierDismissible = true,
}) async {
final l10n = AppLocalizations.of(context);
final effectiveConfirmText = confirmText ?? l10n?.confirm ?? 'Confirm';
final effectiveCancelText = cancelText ?? l10n?.cancel ?? 'Cancel';
2025-08-10 01:20:45 +05:30
final result = await showDialog<bool>(
context: context,
barrierDismissible: barrierDismissible,
builder: (ctx) => buildBase(
context: ctx,
title: title,
content: Text(
message,
style: TextStyle(color: ctx.conduitTheme.textSecondary),
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: Text(
effectiveCancelText,
2025-08-10 01:20:45 +05:30
style: TextStyle(color: ctx.conduitTheme.textSecondary),
),
),
TextButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: TextButton.styleFrom(
foregroundColor: isDestructive
? ctx.conduitTheme.error
: ctx.conduitTheme.buttonPrimary,
),
child: Text(
effectiveConfirmText,
2025-08-10 01:20:45 +05:30
style: TextStyle(
color: isDestructive
? ctx.conduitTheme.error
: ctx.conduitTheme.buttonPrimary,
),
),
),
],
),
);
return result ?? false;
}
/// Show a generic themed dialog
static Future<T?> show<T>(
BuildContext context, {
required String title,
required Widget content,
List<Widget>? actions,
bool barrierDismissible = true,
}) {
return showDialog<T>(
context: context,
barrierDismissible: barrierDismissible,
builder: (ctx) => buildBase(
context: ctx,
title: title,
content: content,
actions: actions,
),
);
}
2025-09-07 23:48:47 +05:30
/// 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,
String? cancelText,
2025-09-07 23:48:47 +05:30
bool barrierDismissible = true,
TextInputType? keyboardType,
TextCapitalization textCapitalization = TextCapitalization.sentences,
int? maxLength,
}) async {
final theme = context.conduitTheme;
final controller = TextEditingController(text: initialValue ?? '');
final l10n = AppLocalizations.of(context);
final effectiveConfirmText = confirmText ?? l10n?.save ?? 'Save';
final effectiveCancelText = cancelText ?? l10n?.cancel ?? 'Cancel';
2025-09-07 23:48:47 +05:30
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),
2025-09-24 12:00:49 +05:30
borderSide: BorderSide(
color: theme.buttonPrimary,
width: 1,
),
2025-09-07 23:48:47 +05:30
),
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.md,
),
),
onSubmitted: (v) {
final trimmed = v.trim();
2025-09-24 12:00:49 +05:30
final unchanged =
(initialValue != null && trimmed == initialValue.trim());
2025-09-07 23:48:47 +05:30
if (trimmed.isEmpty || unchanged) return;
Navigator.of(ctx).pop(trimmed);
},
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text(
effectiveCancelText,
2025-09-07 23:48:47 +05:30
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(
effectiveConfirmText,
2025-09-07 23:48:47 +05:30
style: TextStyle(
color: enabled
? theme.buttonPrimary
: theme.textSecondary,
),
),
);
},
),
],
);
},
);
return result;
}
2025-08-10 01:20:45 +05:30
}