amoriai/lib/services/dialog_service.dart

262 lines
7.3 KiB
Dart

import 'package:flutter/material.dart';
import '../core/theme/app_theme.dart';
enum NotificationType { success, error, warning, info }
class DialogService {
// Singleton pattern
static final DialogService _instance = DialogService._internal();
factory DialogService() {
return _instance;
}
DialogService._internal();
// Global keys
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Helper method to get context (use with caution, better to pass context or use keys)
BuildContext? get _context => navigatorKey.currentContext;
// --- SnackBar Notifications ---
void showSnackBar({
required String message,
NotificationType type = NotificationType.info,
Duration duration = const Duration(seconds: 3),
String? actionLabel,
VoidCallback? onAction,
}) {
if (scaffoldMessengerKey.currentState == null) {
debugPrint(
'⚠️ ScaffoldMessengerState is null, cannot show SnackBar: $message',
);
return;
}
Color backgroundColor;
Color textColor = Colors.white;
IconData icon;
switch (type) {
case NotificationType.success:
backgroundColor = AppTheme.success;
icon = Icons.check_circle_outline;
break;
case NotificationType.error:
backgroundColor = AppTheme.error.withOpacity(0.9);
icon = Icons.error_outline;
break;
case NotificationType.warning:
backgroundColor = AppTheme.warning;
textColor = Colors.black87; // Dark text for yellow background
icon = Icons.warning_amber_rounded;
break;
case NotificationType.info:
backgroundColor = AppTheme.primaryBrown;
icon = Icons.info_outline;
break;
}
scaffoldMessengerKey.currentState!.removeCurrentSnackBar();
scaffoldMessengerKey.currentState!.showSnackBar(
SnackBar(
content: Row(
children: [
Icon(icon, color: textColor, size: 24),
const SizedBox(width: 12),
Expanded(
child: Text(
message,
style: TextStyle(
color: textColor,
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
),
),
),
],
),
backgroundColor: backgroundColor,
duration: duration,
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
action: actionLabel != null
? SnackBarAction(
label: actionLabel,
textColor: textColor,
onPressed: onAction ?? () {},
)
: null,
),
);
}
void showSuccess(String message) {
showSnackBar(message: message, type: NotificationType.success);
}
void showError(String message) {
showSnackBar(message: message, type: NotificationType.error);
}
void showWarning(String message) {
showSnackBar(message: message, type: NotificationType.warning);
}
void showInfo(String message) {
showSnackBar(message: message, type: NotificationType.info);
}
// --- Dialogs ---
Future<void> showAlertDialog({
required String title,
required String message,
String buttonText = 'OK',
BuildContext? context,
}) async {
final ctx = context ?? _context;
if (ctx == null) return;
return showDialog(
context: ctx,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
content: Text(message),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
actions: [
TextButton(
child: Text(buttonText),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Future<bool> showConfirmDialog({
required String title,
required String message,
String confirmText = 'Ya',
String cancelText = 'Batal',
bool isDanger = false,
BuildContext? context,
}) async {
final ctx = context ?? _context;
if (ctx == null) return false;
return await showDialog<bool>(
context: ctx,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
content: Text(message),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
actions: [
TextButton(
child: Text(
cancelText,
style: const TextStyle(color: Colors.grey),
),
onPressed: () {
Navigator.of(context).pop(false);
},
),
TextButton(
child: Text(
confirmText,
style: TextStyle(
color: isDanger ? AppTheme.error : AppTheme.primaryBrown,
fontWeight: FontWeight.bold,
),
),
onPressed: () {
Navigator.of(context).pop(true);
},
),
],
);
},
) ??
false;
}
Future<void> showLoadingDialog({
String message = 'Memuat...',
BuildContext? context,
}) async {
final ctx = context ?? _context;
if (ctx == null) return;
showDialog(
context: ctx,
barrierDismissible: false,
builder: (BuildContext context) {
return PopScope(
canPop: false, // Prevent back button
child: Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(color: AppTheme.primaryBrown),
const SizedBox(height: 20),
Text(
message,
style: const TextStyle(
fontFamily: 'Poppins',
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
);
},
);
}
void hideLoadingDialog({BuildContext? context}) {
final ctx = context ?? _context;
if (ctx == null) {
// If we don't have context but assume the navigator is attached
if (navigatorKey.currentState?.canPop() ?? false) {
navigatorKey.currentState?.pop();
}
return;
}
Navigator.of(ctx).pop();
}
}