marco.pms.mobileapp/lib/helpers/widgets/my_confirmation_dialog.dart

303 lines
8.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:on_field_work/helpers/widgets/my_text.dart';
import 'package:on_field_work/helpers/widgets/my_snackbar.dart';
class ConfirmDialog extends StatelessWidget {
final String title;
final String message;
/// Text for confirm button (default: "Delete")
final String confirmText;
/// Text for cancel button (default: "Cancel")
final String cancelText;
/// Icon shown in the dialog header (default: Icons.delete)
final IconData icon;
/// Icon for confirm button (default: Icons.delete_forever)
final IconData confirmIcon;
/// Icon for cancel button (default: Icons.close)
final IconData cancelIcon;
/// Background color for confirm button (default: Colors.redAccent)
final Color confirmColor;
/// Callback fired when confirm is pressed and awaited.
final Future<void> Function() onConfirm;
/// External RxBool to observe the loading state, if null internal is used
final RxBool? isProcessing;
/// Custom error message shown in snackbar if confirmation fails
final String errorMessage;
/// Text shown in confirm button while loading
final String loadingText;
const ConfirmDialog({
super.key,
required this.title,
required this.message,
required this.onConfirm,
this.confirmText = "Delete",
this.cancelText = "Cancel",
this.icon = Icons.delete,
this.confirmIcon = Icons.delete_forever,
this.cancelIcon = Icons.close,
this.confirmColor = Colors.redAccent,
this.isProcessing,
this.errorMessage = "Failed to complete action. Try again.",
this.loadingText = "Submitting…",
});
@override
Widget build(BuildContext context) {
final RxBool loading = isProcessing ?? false.obs;
final theme = Theme.of(context);
return Dialog(
backgroundColor: theme.colorScheme.surface,
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 280,
maxWidth: 480,
),
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 8),
child: _ContentView(
title: title,
message: message,
icon: icon,
confirmColor: confirmColor,
confirmText: confirmText,
cancelText: cancelText,
confirmIcon: confirmIcon,
cancelIcon: cancelIcon,
loading: loading,
onConfirm: onConfirm,
errorMessage: errorMessage,
loadingText: loadingText,
),
),
),
);
}
}
class _ContentView extends StatelessWidget {
final String title, message, confirmText, cancelText, loadingText;
final IconData icon, confirmIcon, cancelIcon;
final Color confirmColor;
final RxBool loading;
final Future<void> Function() onConfirm;
final String errorMessage;
const _ContentView({
required this.title,
required this.message,
required this.icon,
required this.confirmColor,
required this.confirmText,
required this.cancelText,
required this.confirmIcon,
required this.cancelIcon,
required this.loading,
required this.onConfirm,
required this.errorMessage,
required this.loadingText,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: confirmColor.withOpacity(0.12),
shape: BoxShape.circle,
),
padding: const EdgeInsets.all(10),
child: Icon(
icon,
size: 22,
color: confirmColor,
),
),
const SizedBox(width: 12),
Expanded(
child: MyText.titleLarge(
title,
fontWeight: 700,
color: colorScheme.onSurface,
),
),
],
),
const SizedBox(height: 12),
MyText.bodyMedium(
message,
textAlign: TextAlign.left,
color: colorScheme.onSurface.withValues(alpha: 0.75),
),
const SizedBox(height: 20),
Divider(
height: 1,
color: colorScheme.outlineVariant.withValues(alpha: 0.6),
),
const SizedBox(height: 12),
Align(
alignment: Alignment.center,
child: SizedBox(
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Obx(
() => _DialogButton(
text: cancelText,
icon: cancelIcon,
color: Colors.transparent,
textColor: colorScheme.onSurface,
isFilled: false,
isLoading: false,
onPressed: loading.value
? null
: () => Navigator.pop(context, false),
),
),
),
const SizedBox(width: 20),
Expanded(
child: Obx(
() => _DialogButton(
text: loading.value ? loadingText : confirmText,
icon: confirmIcon,
color: confirmColor,
textColor: Colors.white,
isFilled: true,
isLoading: loading.value,
onPressed: () async {
try {
loading.value = true;
await onConfirm();
Navigator.pop(context, true);
} catch (e) {
showAppSnackbar(
title: "Error",
message: errorMessage,
type: SnackbarType.error,
);
} finally {
loading.value = false;
}
},
),
),
),
],
),
),
),
],
);
}
}
class _DialogButton extends StatelessWidget {
final String text;
final IconData icon;
final Color color;
final Color textColor;
final bool isFilled;
final VoidCallback? onPressed;
final bool isLoading;
const _DialogButton({
required this.text,
required this.icon,
required this.color,
required this.textColor,
required this.isFilled,
required this.onPressed,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final ButtonStyle style = isFilled
? ElevatedButton.styleFrom(
backgroundColor: color,
foregroundColor: textColor,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(999),
),
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 10),
)
: OutlinedButton.styleFrom(
foregroundColor: textColor,
side: BorderSide(
color: theme.colorScheme.outline.withValues(alpha: 0.7),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(999),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
);
final Widget iconWidget = isLoading
? SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: isFilled ? Colors.white : theme.colorScheme.primary,
),
)
: Icon(icon, size: 18);
final Widget labelWidget = MyText.bodyMedium(
text,
color: isFilled ? Colors.white : textColor,
fontWeight: 600,
);
final child = Row(
mainAxisSize: MainAxisSize.min,
children: [
iconWidget,
const SizedBox(width: 8),
Flexible(child: labelWidget),
],
);
return isFilled
? ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: style,
child: child,
)
: OutlinedButton(
onPressed: isLoading ? null : onPressed,
style: style,
child: child,
);
}
}