199 lines
4.9 KiB
Dart
199 lines
4.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../i18n/strings.g.dart';
|
|
|
|
/// Utility functions for showing common dialogs
|
|
|
|
/// Shows a loading dialog that cannot be dismissed by tapping outside.
|
|
///
|
|
/// Returns a function to close the dialog when the operation completes.
|
|
/// Usage:
|
|
/// ```dart
|
|
/// final close = showLoadingDialog(context);
|
|
/// try {
|
|
/// await performOperation();
|
|
/// } finally {
|
|
/// close();
|
|
/// }
|
|
/// ```
|
|
VoidCallback showLoadingDialog(BuildContext context, {String? message}) {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => PopScope(
|
|
canPop: false,
|
|
child: Center(
|
|
child: message != null
|
|
? Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const CircularProgressIndicator(),
|
|
const SizedBox(height: 16),
|
|
Material(
|
|
color: Colors.transparent,
|
|
child: Text(
|
|
message,
|
|
style: const TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: const CircularProgressIndicator(),
|
|
),
|
|
),
|
|
);
|
|
|
|
return () {
|
|
if (context.mounted) {
|
|
Navigator.of(context, rootNavigator: true).pop();
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Shows a generic confirmation dialog with customizable button text.
|
|
/// Returns true if confirmed, false if cancelled.
|
|
Future<bool> showConfirmation(
|
|
BuildContext context, {
|
|
required String title,
|
|
required String message,
|
|
String? confirmText,
|
|
String? cancelText,
|
|
Color? confirmColor,
|
|
}) async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text(title),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: Text(cancelText ?? t.common.cancel),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
style: confirmColor != null
|
|
? TextButton.styleFrom(foregroundColor: confirmColor)
|
|
: null,
|
|
child: Text(confirmText ?? t.common.confirm),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
return confirmed ?? false;
|
|
}
|
|
|
|
/// Shows a delete confirmation dialog
|
|
/// Returns true if user confirmed, false if cancelled
|
|
Future<bool> showDeleteConfirmation(
|
|
BuildContext context, {
|
|
required String title,
|
|
required String message,
|
|
}) async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text(title),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: Text(t.common.cancel),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
|
child: Text(t.common.delete),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
return confirmed ?? false;
|
|
}
|
|
|
|
/// Shows a text input dialog for creating/naming items
|
|
/// Returns the entered text, or null if cancelled
|
|
Future<String?> showTextInputDialog(
|
|
BuildContext context, {
|
|
required String title,
|
|
required String labelText,
|
|
required String hintText,
|
|
String? initialValue,
|
|
}) async {
|
|
return showDialog<String>(
|
|
context: context,
|
|
builder: (context) => _TextInputDialog(
|
|
title: title,
|
|
labelText: labelText,
|
|
hintText: hintText,
|
|
initialValue: initialValue,
|
|
),
|
|
);
|
|
}
|
|
|
|
class _TextInputDialog extends StatefulWidget {
|
|
final String title;
|
|
final String labelText;
|
|
final String hintText;
|
|
final String? initialValue;
|
|
|
|
const _TextInputDialog({
|
|
required this.title,
|
|
required this.labelText,
|
|
required this.hintText,
|
|
this.initialValue,
|
|
});
|
|
|
|
@override
|
|
State<_TextInputDialog> createState() => _TextInputDialogState();
|
|
}
|
|
|
|
class _TextInputDialogState extends State<_TextInputDialog> {
|
|
late final TextEditingController _controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = TextEditingController(text: widget.initialValue);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _submit() {
|
|
if (_controller.text.isNotEmpty) {
|
|
Navigator.pop(context, _controller.text);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AlertDialog(
|
|
title: Text(widget.title),
|
|
content: TextField(
|
|
controller: _controller,
|
|
autofocus: true,
|
|
decoration: InputDecoration(
|
|
labelText: widget.labelText,
|
|
hintText: widget.hintText,
|
|
),
|
|
onSubmitted: (_) => _submit(),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text(t.common.cancel),
|
|
),
|
|
TextButton(
|
|
onPressed: _submit,
|
|
child: Text(t.common.save),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|