- Updated import paths across multiple files to reflect the new package name. - Changed application name and identifiers in CMakeLists.txt, Runner.rc, and other configuration files. - Modified web index.html and manifest.json to update the app title and name. - Adjusted macOS and Windows project settings to align with the new application name. - Ensured consistency in naming across all relevant files and directories.
302 lines
8.4 KiB
Dart
302 lines
8.4 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(24, 20, 24, 16),
|
|
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, // allow full available width
|
|
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,
|
|
);
|
|
}
|
|
}
|