This commit is contained in:
Surendiran 2026-07-14 18:15:26 +05:30
parent a4634a9ba2
commit afe9a0736d
47 changed files with 701 additions and 187 deletions

View File

@ -5,6 +5,7 @@ import 'package:responsive_framework/responsive_framework.dart';
import 'core/constants/app_constants.dart';
import 'core/theme/theme_provider.dart';
import 'shared/routes/app_router.dart';
import 'shared/widgets/app_toast.dart';
class BharatErpApp extends ConsumerWidget {
const BharatErpApp({super.key});
@ -23,14 +24,16 @@ class BharatErpApp extends ConsumerWidget {
themeMode: resolveThemeMode(themeMode),
themeAnimationDuration: Duration.zero,
routerConfig: router,
builder: (context, child) => ResponsiveBreakpoints.builder(
child: child!,
breakpoints: const [
Breakpoint(start: 0, end: 599, name: MOBILE),
Breakpoint(start: 600, end: 1023, name: TABLET),
Breakpoint(start: 1024, end: 1439, name: DESKTOP),
Breakpoint(start: 1440, end: double.infinity, name: '4K'),
],
builder: (context, child) => AppToastHost(
child: ResponsiveBreakpoints.builder(
child: child ?? const SizedBox.shrink(),
breakpoints: const [
Breakpoint(start: 0, end: 599, name: MOBILE),
Breakpoint(start: 600, end: 1023, name: TABLET),
Breakpoint(start: 1024, end: 1439, name: DESKTOP),
Breakpoint(start: 1440, end: double.infinity, name: '4K'),
],
),
),
);
}

View File

@ -196,8 +196,13 @@ class AppTheme {
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
contentTextStyle: textTheme.bodyMedium?.copyWith(color: colorScheme.onInverseSurface),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 0,
backgroundColor: colorScheme.inverseSurface,
contentTextStyle: textTheme.bodyMedium?.copyWith(
color: colorScheme.onInverseSurface,
),
insetPadding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
),
dataTableTheme: DataTableThemeData(
headingRowColor: WidgetStateProperty.all(

View File

@ -24,6 +24,7 @@ import '../providers/assets_provider.dart';
import '../providers/asset_form_lookups_provider.dart';
import '../widgets/asset_form_panel.dart';
import '../widgets/asset_side_panels.dart';
import '../../../../shared/widgets/app_toast.dart';
class AssetDetailScreen extends ConsumerStatefulWidget {
const AssetDetailScreen({super.key, required this.assetId});
@ -161,7 +162,7 @@ class _AssetDetailScreenState extends ConsumerState<AssetDetailScreen>
width: 520,
);
if (transferred == true && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Asset transferred successfully')),
);
}
@ -476,7 +477,7 @@ class _AmcTab extends ConsumerWidget {
width: 520,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('AMC contract created')),
);
}
@ -493,7 +494,7 @@ class _AmcTab extends ConsumerWidget {
width: 520,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('AMC contract updated')),
);
}
@ -571,7 +572,7 @@ class _ServiceVisitsTab extends ConsumerWidget {
width: 560,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Service visit logged')),
);
}
@ -592,7 +593,7 @@ class _ServiceVisitsTab extends ConsumerWidget {
width: 560,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Service visit updated')),
);
}
@ -662,7 +663,7 @@ class _InsuranceTab extends ConsumerWidget {
width: 520,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Insurance policy created')),
);
}
@ -679,7 +680,7 @@ class _InsuranceTab extends ConsumerWidget {
width: 520,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Insurance policy updated')),
);
}

View File

@ -26,6 +26,7 @@ import '../providers/asset_categories_provider.dart';
import '../providers/asset_form_lookups_provider.dart';
import '../providers/assets_provider.dart';
import '../widgets/asset_form_panel.dart';
import '../../../../shared/widgets/app_toast.dart';
class AssetListScreen extends ConsumerStatefulWidget {
const AssetListScreen({super.key});
@ -49,10 +50,10 @@ class _AssetListScreenState extends ConsumerState<AssetListScreen> {
final error = next.valueOrNull?.actionError;
final success = next.valueOrNull?.actionSuccess;
if (error != null && error != prev?.valueOrNull?.actionError) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
}
if (success != null && success != prev?.valueOrNull?.actionSuccess) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(success)));
showAppToastFromSnackBar(context, SnackBar(content: Text(success)));
}
});

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../shared/widgets/page_header.dart';
import '../../../../shared/widgets/app_toast.dart';
class AssetQrGenerateScreen extends StatelessWidget {
const AssetQrGenerateScreen({super.key});
@ -32,7 +33,7 @@ class AssetQrGenerateScreen extends StatelessWidget {
const SizedBox(height: 16),
FilledButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('Print command hooked for platform print service.'),
),

View File

@ -23,6 +23,7 @@ import '../../data/repositories/asset_repository_impl.dart';
import '../providers/asset_categories_provider.dart';
import '../providers/asset_form_lookups_provider.dart';
import '../providers/assets_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
Future<void> openAssetFormPanel(
BuildContext context,
@ -37,7 +38,7 @@ Future<void> openAssetFormPanel(
width: panelWidth,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
assetId == null

View File

@ -25,6 +25,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart';
import '../providers/audit_provider.dart';
import '../widgets/audit_log_detail_panel.dart';
import '../../../../shared/widgets/app_toast.dart';
class AuditLogsScreen extends ConsumerStatefulWidget {
const AuditLogsScreen({super.key});
@ -49,7 +50,7 @@ class _AuditLogsScreenState extends ConsumerState<AuditLogsScreen> {
if (file == null) {
final error = ref.read(auditLogsListProvider).valueOrNull?.actionError;
if (error != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
}
return;
}
@ -60,7 +61,7 @@ class _AuditLogsScreenState extends ConsumerState<AuditLogsScreen> {
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(saved ? 'Downloaded ${file.fileName}' : 'Export cancelled'),
),
@ -111,7 +112,7 @@ class _AuditLogsScreenState extends ConsumerState<AuditLogsScreen> {
ref.listen(auditLogsListProvider, (prev, next) {
final error = next.valueOrNull?.actionError;
if (error != null && error != prev?.valueOrNull?.actionError) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
}
});

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import '../../../../core/utils/validators.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/app_text_field.dart';
import '../../../../shared/widgets/app_toast.dart';
class ChangePasswordScreen extends StatefulWidget {
const ChangePasswordScreen({super.key});
@ -66,7 +67,7 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
label: 'Update Password',
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Password updated (API pending)')),
);
Navigator.of(context).pop();

View File

@ -14,6 +14,7 @@ import '../../../../shared/widgets/app_button.dart';
import '../widgets/bcpl_logo.dart';
import '../widgets/login_colors.dart';
import '../widgets/login_hero_panel.dart';
import '../../../../shared/widgets/app_toast.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@ -87,7 +88,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
context.go(RouteConstants.dashboard);
} else {
final error = ref.read(authStateProvider).error;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(error ?? 'Invalid credentials')),
);
}

View File

@ -5,6 +5,7 @@ import '../../../../core/constants/route_constants.dart';
import '../../../../core/utils/validators.dart';
import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/app_text_field.dart';
import '../../../../shared/widgets/app_toast.dart';
class ResetPasswordScreen extends StatefulWidget {
const ResetPasswordScreen({super.key});
@ -59,7 +60,7 @@ class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
label: 'Reset Password',
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Password reset (API token flow pending)')),
);
context.go(RouteConstants.login);

View File

@ -18,6 +18,7 @@ import '../providers/grn_provider.dart';
import '../widgets/grn_attachments_card.dart';
import '../widgets/grn_line_items_editor.dart';
import '../widgets/grn_status_chip.dart';
import '../../../../shared/widgets/app_toast.dart';
class GrnDetailScreen extends ConsumerStatefulWidget {
const GrnDetailScreen({super.key, required this.grnId});
@ -116,13 +117,11 @@ class _GrnDetailScreenState extends ConsumerState<GrnDetailScreen> {
try {
await action();
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(success)));
showAppToastFromSnackBar(context, SnackBar(content: Text(success)));
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isWorking = false);
@ -186,13 +185,11 @@ class _GrnDetailScreenState extends ConsumerState<GrnDetailScreen> {
fileName: '${grn.grnNumber ?? 'GRN-${grn.id}'}.pdf',
);
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('PDF downloaded')));
showAppToastFromSnackBar(context, const SnackBar(content: Text('PDF downloaded')));
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isDownloadingPdf = false);

View File

@ -25,6 +25,7 @@ import '../providers/grn_lookups_provider.dart';
import '../providers/grn_provider.dart';
import '../widgets/grn_line_items_editor.dart';
import '../widgets/grn_status_chip.dart';
import '../../../../shared/widgets/app_toast.dart';
class GrnFormScreen extends ConsumerStatefulWidget {
const GrnFormScreen({super.key, this.grnId});
@ -226,7 +227,7 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
if (formState == null) return;
if (!formState.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('Please fix the highlighted errors before saving'),
),
@ -236,15 +237,14 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
if (!widget.isEditing) {
if (_selectedPoId == null || _warehouseId == null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please select PO and warehouse')),
);
return;
}
final lineError = _lineItemsError();
if (lineError != null) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(lineError)));
showAppToastFromSnackBar(context, SnackBar(content: Text(lineError)));
return;
}
}
@ -263,7 +263,7 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(widget.isEditing ? 'GRN updated' : 'GRN created'),
),
@ -279,8 +279,7 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
if (!mounted) return;
final message =
e is Failure ? validationErrorMessage(e) : e.toString();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message)));
showAppToastFromSnackBar(context, SnackBar(content: Text(message)));
} finally {
if (mounted) setState(() => _isSubmitting = false);
}
@ -443,7 +442,7 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
}
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(e.toString())),
);
}

View File

@ -23,6 +23,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart';
import '../providers/grn_provider.dart';
import '../widgets/grn_status_chip.dart';
import '../../../../shared/widgets/app_toast.dart';
class GrnListScreen extends ConsumerStatefulWidget {
const GrnListScreen({super.key});
@ -137,7 +138,7 @@ class _GrnListScreenState extends ConsumerState<GrnListScreen> {
void _editGrn(GrnModel grn) {
if (!grn.canEdit) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Only posted GRNs can be edited')),
);
return;

View File

@ -9,6 +9,7 @@ import '../../../../shared/models/grn_model.dart';
import '../../../../shared/utils/file_download_helper.dart';
import '../../../../shared/widgets/app_confirmation_dialog.dart';
import '../providers/grn_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
const _allowedExtensions = ['pdf', 'jpg', 'jpeg', 'png', 'webp'];
@ -65,7 +66,7 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
final bytes = file.bytes;
if (bytes == null || bytes.isEmpty) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Could not read the selected file')),
);
return;
@ -74,7 +75,7 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
final ext = (file.extension ?? '').toLowerCase();
if (!_allowedExtensions.contains(ext)) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('Only PDF, JPEG, PNG, and WebP files are allowed'),
),
@ -89,14 +90,14 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
filename: file.name,
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text('${file.name} uploaded')),
);
} catch (e) {
if (!mounted) return;
final message =
e is Failure ? validationErrorMessage(e) : e.toString();
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(message)),
);
} finally {
@ -117,7 +118,7 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(e.toString())),
);
} finally {
@ -144,14 +145,14 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
.read(grnDetailProvider(widget.grn.id).notifier)
.deleteAttachment(attachment.id);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Attachment deleted')),
);
} catch (e) {
if (!mounted) return;
final message =
e is Failure ? validationErrorMessage(e) : e.toString();
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(message)),
);
} finally {

View File

@ -22,6 +22,7 @@ import '../../../../shared/widgets/page_header.dart';
import '../../domain/entities/master_definition.dart';
import '../providers/master_provider.dart';
import '../widgets/master_form_panel.dart';
import '../../../../shared/widgets/app_toast.dart';
class MasterListScreen extends ConsumerStatefulWidget {
const MasterListScreen({super.key, required this.masterId});
@ -63,7 +64,7 @@ class _MasterListScreenState extends ConsumerState<MasterListScreen> {
if (file == null) {
final error = ref.read(masterListProvider(widget.masterId)).valueOrNull?.actionError;
if (error != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(error)),
);
}
@ -76,7 +77,7 @@ class _MasterListScreenState extends ConsumerState<MasterListScreen> {
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
saved ? 'Downloaded ${file.fileName}' : 'Export cancelled',
@ -105,7 +106,7 @@ class _MasterListScreenState extends ConsumerState<MasterListScreen> {
);
if (saved != null && mounted) {
ref.invalidate(masterListProvider(widget.masterId));
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
recordId == null
@ -134,7 +135,7 @@ class _MasterListScreenState extends ConsumerState<MasterListScreen> {
await ref.read(masterListProvider(widget.masterId).notifier).deleteRecord(id);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
success

View File

@ -12,6 +12,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../domain/entities/master_definition.dart';
import '../providers/master_provider.dart';
import 'master_quick_add.dart';
import '../../../../shared/widgets/app_toast.dart';
class MasterFormPanel extends ConsumerStatefulWidget {
const MasterFormPanel({
@ -69,7 +70,7 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
}
final error = ref.read(masterFormProvider(_args)).valueOrNull?.errorMessage;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
error ??

View File

@ -10,6 +10,7 @@ import '../../../../shared/widgets/app_searchable_dropdown.dart';
import '../../../../shared/widgets/error_view.dart';
import '../../domain/entities/master_definition.dart';
import '../providers/master_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
/// Compact create form for inline Quick Add (no side panel / dialog).
class MasterInlineCreateForm extends ConsumerStatefulWidget {
@ -63,7 +64,7 @@ class _MasterInlineCreateFormState
}
final error = ref.read(masterFormProvider(_args)).valueOrNull?.errorMessage;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
error ?? 'Failed to create ${_definition.title.toLowerCase()}',

View File

@ -8,6 +8,7 @@ import '../../../../shared/widgets/app_searchable_dropdown.dart';
import '../../../../shared/widgets/app_side_panel.dart';
import '../providers/master_provider.dart';
import 'master_inline_create_form.dart';
import '../../../../shared/widgets/app_toast.dart';
/// Dropdown with inline Quick Add that expands below the field (no dialog).
///
@ -72,7 +73,7 @@ class _MasterQuickAddDropdownState<T>
void _openInlineForm() {
if (!_canQuickAdd) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('You do not have permission to add master data'),
),
@ -126,7 +127,7 @@ class _MasterQuickAddDropdownState<T>
_collapse();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content:
Text('${_titleCase(masterQuickAddNoun(widget.masterId))} added'),

View File

@ -21,6 +21,7 @@ import '../providers/purchase_orders_provider.dart';
import '../../data/repositories/purchase_order_repository_impl.dart';
import '../widgets/po_status_chip.dart';
import '../widgets/purchase_order_line_items_editor.dart';
import '../../../../shared/widgets/app_toast.dart';
class PurchaseOrderDetailScreen extends ConsumerStatefulWidget {
const PurchaseOrderDetailScreen({super.key, required this.purchaseOrderId});
@ -145,13 +146,11 @@ class _PurchaseOrderDetailScreenState
try {
await action();
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(success)));
showAppToastFromSnackBar(context, SnackBar(content: Text(success)));
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isWorking = false);
@ -203,7 +202,7 @@ class _PurchaseOrderDetailScreenState
final remarks = remarksController.text.trim();
remarksController.dispose();
if (remarks.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Rejection remarks are required')),
);
return;
@ -231,15 +230,14 @@ class _PurchaseOrderDetailScreenState
.read(purchaseOrderDetailProvider(widget.purchaseOrderId).notifier)
.amend();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Amendment draft created')),
);
context.go('${RouteConstants.purchaseOrders}/${amended.id}');
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isWorking = false);
@ -281,15 +279,14 @@ class _PurchaseOrderDetailScreenState
if (result.failure != null) throw result.failure!;
ref.invalidate(purchaseOrdersListProvider);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Purchase order deleted')),
);
context.go(RouteConstants.purchaseOrders);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isWorking = false);
@ -309,8 +306,7 @@ class _PurchaseOrderDetailScreenState
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isDownloadingPdf = false);

View File

@ -25,6 +25,7 @@ import '../providers/purchase_order_lookups_provider.dart';
import '../providers/purchase_orders_provider.dart';
import '../widgets/po_status_chip.dart';
import '../widgets/purchase_order_line_items_editor.dart';
import '../../../../shared/widgets/app_toast.dart';
class PurchaseOrderFormScreen extends ConsumerStatefulWidget {
const PurchaseOrderFormScreen({super.key, this.purchaseOrderId});
@ -235,7 +236,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
final isValid = formState.validate();
if (!isValid) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('Please fix the highlighted errors before saving'),
),
@ -244,7 +245,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
}
if (_poType == null || _vendorId == null || _plantId == null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please complete all required fields')),
);
return;
@ -252,14 +253,14 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
final lineItemsError = _lineItemsError();
if (lineItemsError != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(lineItemsError)),
);
return;
}
if (_lines.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Add at least one line item')),
);
return;
@ -283,7 +284,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
widget.isEditing ? 'Purchase order updated' : 'Purchase order created',
@ -302,7 +303,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
final message = e is Failure
? validationErrorMessage(e)
: e.toString();
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(message)),
);
} finally {

View File

@ -25,6 +25,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart';
import '../providers/purchase_orders_provider.dart';
import '../widgets/po_status_chip.dart';
import '../../../../shared/widgets/app_toast.dart';
class PurchaseOrderListScreen extends ConsumerStatefulWidget {
const PurchaseOrderListScreen({super.key});
@ -63,10 +64,10 @@ class _PurchaseOrderListScreenState extends ConsumerState<PurchaseOrderListScree
final error = next.valueOrNull?.actionError;
final success = next.valueOrNull?.actionSuccess;
if (error != null && error != prev?.valueOrNull?.actionError) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
}
if (success != null && success != prev?.valueOrNull?.actionSuccess) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(success)));
showAppToastFromSnackBar(context, SnackBar(content: Text(success)));
}
});
@ -175,7 +176,7 @@ class _PurchaseOrderListScreenState extends ConsumerState<PurchaseOrderListScree
void _editOrder(PurchaseOrderModel order) {
if (!order.canEdit) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('Only draft or rejected orders can be edited'),
),
@ -187,7 +188,7 @@ class _PurchaseOrderListScreenState extends ConsumerState<PurchaseOrderListScree
Future<void> _deleteOrder(PurchaseOrderModel order) async {
if (!order.canDelete) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('Only draft or rejected orders can be deleted'),
),

View File

@ -30,6 +30,7 @@ import '../../../roles/presentation/providers/roles_provider.dart';
import '../widgets/add_user_panel.dart';
import '../widgets/create_role_panel.dart';
import '../widgets/rbac_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
RbacTab rbacTabFromLocation(String location) {
if (location.startsWith(RouteConstants.roles)) return RbacTab.roles;
@ -69,7 +70,7 @@ class _UsersRoleManagementScreenState
);
if (saved == true && mounted) {
ref.invalidate(usersListProvider);
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
userId == null ? 'User added successfully' : 'User updated successfully',
@ -90,7 +91,7 @@ class _UsersRoleManagementScreenState
if (saved == true && mounted) {
ref.invalidate(rolesListProvider);
ref.invalidate(usersListProvider);
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
roleId == null ? 'Role created successfully' : 'Role updated successfully',
@ -106,7 +107,7 @@ class _UsersRoleManagementScreenState
Future<void> _deleteRole(RoleCardModel role) async {
if (isProtectedRole(role)) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Super Admin role cannot be deleted')),
);
return;
@ -128,7 +129,7 @@ class _UsersRoleManagementScreenState
ref.invalidate(usersListProvider);
}
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(success ? 'Role deleted successfully' : 'Failed to delete role'),
),
@ -594,7 +595,7 @@ class _UsersTabState extends ConsumerState<_UsersTab> {
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
result.failure == null
@ -612,7 +613,7 @@ class _UsersTabState extends ConsumerState<_UsersTab> {
if (file == null) {
final error = ref.read(usersListProvider).valueOrNull?.actionError;
if (error != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(error)),
);
}
@ -625,7 +626,7 @@ class _UsersTabState extends ConsumerState<_UsersTab> {
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
saved ? 'Downloaded ${file.fileName}' : 'Export cancelled',
@ -647,7 +648,7 @@ class _UsersTabState extends ConsumerState<_UsersTab> {
final success = await ref.read(usersListProvider.notifier).deactivateUser(user.id);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(success ? 'User deactivated' : 'Failed to deactivate user'),
),
@ -1232,7 +1233,7 @@ class _PermissionMatrixTabState extends ConsumerState<_PermissionMatrixTab> {
if (!mounted) return;
setState(() => _isSaving = false);
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Row(
children: [

View File

@ -14,6 +14,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/app_side_panel.dart';
import '../../../master_data/presentation/widgets/master_quick_add.dart';
import '../providers/add_user_form_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
class AddUserPanel extends ConsumerStatefulWidget {
const AddUserPanel({super.key, this.userId});
@ -109,7 +110,7 @@ class _AddUserPanelState extends ConsumerState<AddUserPanel> {
.whereType<int>()
.toList();
if (roleIds.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please select at least one role')),
);
return;
@ -169,7 +170,7 @@ class _AddUserPanelState extends ConsumerState<AddUserPanel> {
}
final error = ref.read(addUserFormProvider(widget.userId)).valueOrNull?.errorMessage;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
error ?? (widget.isEditing ? 'Failed to update user' : 'Failed to create user'),

View File

@ -12,6 +12,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/app_side_panel.dart';
import '../providers/role_form_provider.dart';
import 'rbac_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
class RoleFormPanel extends ConsumerStatefulWidget {
const RoleFormPanel({super.key, this.roleId});
@ -82,7 +83,7 @@ class _RoleFormPanelState extends ConsumerState<RoleFormPanel> {
}
final error = ref.read(roleFormProvider(widget.roleId)).valueOrNull?.errorMessage;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
error ??

View File

@ -24,6 +24,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart';
import '../../domain/entities/depreciation_report.dart';
import '../providers/depreciation_report_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
class DepreciationReportScreen extends ConsumerStatefulWidget {
const DepreciationReportScreen({super.key});
@ -52,8 +53,7 @@ class _DepreciationReportScreenState
final error =
ref.read(depreciationReportProvider).valueOrNull?.actionError;
if (error != null) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error)));
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
}
return;
}
@ -64,7 +64,7 @@ class _DepreciationReportScreenState
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
saved ? 'Downloaded ${file.fileName}' : 'Export cancelled',
@ -119,8 +119,7 @@ class _DepreciationReportScreenState
ref.listen(depreciationReportProvider, (prev, next) {
final error = next.valueOrNull?.actionError;
if (error != null && error != prev?.valueOrNull?.actionError) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error)));
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
}
});

View File

@ -11,6 +11,7 @@ import '../../../../shared/widgets/app_loading_view.dart';
import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart';
import '../providers/roles_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
class PermissionMatrixScreen extends ConsumerStatefulWidget {
const PermissionMatrixScreen({super.key, required this.roleId});
@ -30,7 +31,7 @@ class _PermissionMatrixScreenState extends ConsumerState<PermissionMatrixScreen>
await ref.read(permissionMatrixProvider(widget.roleId).notifier).save();
if (!mounted) return;
setState(() => _isSaving = false);
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(success ? 'Permissions saved' : 'Failed to save permissions'),
),

View File

@ -7,6 +7,7 @@ import '../../../../core/theme/theme_provider.dart';
import '../../../../shared/widgets/app_button.dart';
import '../providers/settings_provider.dart';
import '../widgets/settings_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
class AppearanceSettingsScreen extends ConsumerWidget {
const AppearanceSettingsScreen({super.key});
@ -182,7 +183,7 @@ class AppearanceSettingsScreen extends ConsumerWidget {
AppButton(
label: 'Settings Auto-Saved',
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Appearance settings are saved automatically')),
);
},

View File

@ -7,6 +7,7 @@ import '../../../../shared/widgets/app_text_field.dart';
import '../../domain/entities/app_settings.dart';
import '../providers/settings_provider.dart';
import '../widgets/settings_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
class AssetSettingsScreen extends ConsumerStatefulWidget {
const AssetSettingsScreen({super.key});
@ -73,7 +74,7 @@ class _AssetSettingsScreenState extends ConsumerState<AssetSettingsScreen> {
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Asset settings saved')),
);
}

View File

@ -16,6 +16,7 @@ import '../../../../shared/widgets/sidebar_logo.dart';
import '../../domain/entities/app_settings.dart';
import '../providers/settings_provider.dart';
import '../widgets/settings_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
class CompanyProfileSettingsScreen extends ConsumerStatefulWidget {
const CompanyProfileSettingsScreen({super.key});
@ -82,7 +83,7 @@ class _CompanyProfileSettingsScreenState
_applyProfile(ref.read(appSettingsProvider).companyProfile);
setState(() => _loading = false);
if (failure != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(failure.message),
backgroundColor: Theme.of(context).colorScheme.error,
@ -158,7 +159,7 @@ class _CompanyProfileSettingsScreenState
setState(() => _saving = false);
if (failure != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(validationErrorMessage(failure)),
backgroundColor: Theme.of(context).colorScheme.error,
@ -175,7 +176,7 @@ class _CompanyProfileSettingsScreenState
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Company profile saved')),
);
}
@ -207,14 +208,14 @@ class _CompanyProfileSettingsScreenState
uploadedUrl != null &&
uploadedUrl.isNotEmpty) {
setState(() => _logoUrlController.text = uploadedUrl);
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Logo uploaded')),
);
return;
}
if (uploadResult.failure != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(uploadResult.failure!.message),
backgroundColor: Theme.of(context).colorScheme.error,
@ -268,14 +269,14 @@ class _CompanyProfileSettingsScreenState
updateFavicon(previewDataUri);
setState(() => _faviconUrlController.text = uploadedUrl);
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Favicon uploaded')),
);
return;
}
if (uploadResult.failure != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(uploadResult.failure!.message),
backgroundColor: Theme.of(context).colorScheme.error,

View File

@ -8,6 +8,7 @@ import '../../../../shared/widgets/app_text_field.dart';
import '../../domain/entities/app_settings.dart';
import '../providers/settings_provider.dart';
import '../widgets/settings_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
class EmailConfigurationScreen extends ConsumerStatefulWidget {
const EmailConfigurationScreen({super.key});
@ -51,7 +52,7 @@ class _EmailConfigurationScreenState
_applyEmail(ref.read(appSettingsProvider).email);
setState(() => _loading = false);
if (failure != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(failure.message),
backgroundColor: Theme.of(context).colorScheme.error,
@ -87,7 +88,7 @@ class _EmailConfigurationScreenState
final port = int.tryParse(_portController.text.trim());
if (port == null || port <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Enter a valid SMTP port')),
);
return;
@ -109,7 +110,7 @@ class _EmailConfigurationScreenState
setState(() => _saving = false);
if (failure != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(validationErrorMessage(failure)),
backgroundColor: Theme.of(context).colorScheme.error,
@ -118,7 +119,7 @@ class _EmailConfigurationScreenState
return;
}
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Email configuration saved')),
);
}

View File

@ -7,6 +7,7 @@ import '../../../../shared/widgets/app_text_field.dart';
import '../../domain/entities/app_settings.dart';
import '../providers/settings_provider.dart';
import '../widgets/settings_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
class GeneralSettingsScreen extends ConsumerStatefulWidget {
const GeneralSettingsScreen({super.key});
@ -64,7 +65,7 @@ class _GeneralSettingsScreenState extends ConsumerState<GeneralSettingsScreen> {
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('General settings saved')),
);
}

View File

@ -5,6 +5,7 @@ import '../../../../shared/widgets/app_button.dart';
import '../../domain/entities/app_settings.dart';
import '../providers/settings_provider.dart';
import '../widgets/settings_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
class NotificationSettingsScreen extends ConsumerWidget {
const NotificationSettingsScreen({super.key});
@ -16,7 +17,7 @@ class NotificationSettingsScreen extends ConsumerWidget {
Future<void> update(NotificationSettingsConfig updated) async {
await ref.read(appSettingsProvider.notifier).updateNotifications(updated);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Notification settings saved')),
);
}
@ -86,7 +87,7 @@ class NotificationSettingsScreen extends ConsumerWidget {
AppButton(
label: 'Settings Auto-Saved',
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('Notification settings are saved automatically'),
),

View File

@ -8,6 +8,7 @@ import '../../../../shared/widgets/app_text_field.dart';
import '../../domain/entities/app_settings.dart';
import '../providers/settings_provider.dart';
import '../widgets/settings_widgets.dart';
import '../../../../shared/widgets/app_toast.dart';
class SecuritySettingsScreen extends ConsumerStatefulWidget {
const SecuritySettingsScreen({super.key});
@ -86,7 +87,7 @@ class _SecuritySettingsScreenState extends ConsumerState<SecuritySettingsScreen>
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Security settings saved')),
);
}

View File

@ -13,6 +13,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart';
import '../../../../shared/models/user_management_models.dart';
import '../providers/users_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
class UserDetailScreen extends ConsumerWidget {
const UserDetailScreen({super.key, required this.userId});
@ -99,7 +100,7 @@ class UserDetailScreen extends ConsumerWidget {
final success = await ref.read(userDetailProvider(userId).notifier).deactivate();
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(success ? 'User deactivated' : 'Failed to deactivate')),
);
if (success) context.pop();

View File

@ -13,6 +13,7 @@ import '../../../../shared/widgets/app_text_field.dart';
import '../../../../shared/widgets/error_view.dart';
import '../../../master_data/presentation/widgets/master_quick_add.dart';
import '../providers/users_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
class UserFormScreen extends ConsumerStatefulWidget {
const UserFormScreen({super.key, this.userId});
@ -83,7 +84,7 @@ class _UserFormScreenState extends ConsumerState<UserFormScreen> {
Future<void> _submit() async {
if (!_formKey.currentState!.validate()) return;
if (_selectedRoleId == null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please select a role')),
);
return;
@ -127,13 +128,13 @@ class _UserFormScreenState extends ConsumerState<UserFormScreen> {
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(isEditing ? 'User updated' : 'User created')),
);
context.pop();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(e.toString())),
);
} finally {

View File

@ -22,6 +22,7 @@ import '../../../../shared/widgets/app_table_action_icon.dart';
import '../../../../shared/widgets/page_header.dart';
import '../widgets/user_rich_data_table.dart';
import '../providers/users_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
class UserListScreen extends ConsumerStatefulWidget {
const UserListScreen({super.key});
@ -151,7 +152,7 @@ class _UserListScreenState extends ConsumerState<UserListScreen> {
Future<void> _toggleStatus(ManagedUserModel user) async {
final success = await ref.read(usersListProvider.notifier).toggleUserStatus(user);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(success ? 'User status updated' : 'Failed to update status'),
),
@ -170,7 +171,7 @@ class _UserListScreenState extends ConsumerState<UserListScreen> {
final success = await ref.read(usersListProvider.notifier).deactivateUser(user.id);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(success ? 'User deactivated' : 'Failed to deactivate user')),
);
}

View File

@ -12,6 +12,7 @@ import '../../../../shared/widgets/app_button.dart';
import '../../../../shared/widgets/app_text_field.dart';
import '../../../auth/data/repositories/auth_repository_impl.dart';
import '../providers/users_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
class UserProfileScreen extends ConsumerStatefulWidget {
const UserProfileScreen({super.key});
@ -62,7 +63,7 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
);
if (result == null || result.files.isEmpty) return;
setState(() => _avatarUrl = result.files.first.path);
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Image selected. Upload will use avatar_url on save.')),
);
}
@ -84,21 +85,21 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
setState(() => _isUpdatingProfile = false);
if (result.failure != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(result.failure.toString())),
);
return;
}
await ref.read(authStateProvider.notifier).checkAuth();
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Profile updated')),
);
}
Future<void> _changePassword() async {
if (_newPasswordController.text != _confirmPasswordController.text) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Passwords do not match')),
);
return;
@ -106,7 +107,7 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
final passwordError = Validators.password(_newPasswordController.text);
if (passwordError != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(passwordError)));
showAppToastFromSnackBar(context, SnackBar(content: Text(passwordError)));
return;
}
@ -124,7 +125,7 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
setState(() => _isChangingPassword = false);
if (result.failure != null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(result.failure.toString())),
);
return;
@ -133,7 +134,7 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
_currentPasswordController.clear();
_newPasswordController.clear();
_confirmPasswordController.clear();
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Password changed successfully')),
);
}

View File

@ -17,6 +17,7 @@ import '../../../../shared/widgets/page_header.dart';
import '../providers/vendors_provider.dart';
import '../widgets/vendor_form_panel.dart';
import '../widgets/vendor_sub_resource_panels.dart';
import '../../../../shared/widgets/app_toast.dart';
class VendorDetailScreen extends ConsumerStatefulWidget {
const VendorDetailScreen({super.key, required this.vendorId});
@ -171,13 +172,13 @@ class _VendorDetailScreenState extends ConsumerState<VendorDetailScreen>
.read(vendorDetailProvider(widget.vendorId).notifier)
.updateStatus(selected!);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Vendor status updated')),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
}
}
@ -197,7 +198,7 @@ class _VendorDetailScreenState extends ConsumerState<VendorDetailScreen>
if (mounted) context.go(RouteConstants.vendors);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
}
}
@ -382,7 +383,7 @@ class _AddressesTab extends ConsumerWidget {
vendorId: vendorId,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Address saved')),
);
}
@ -450,7 +451,7 @@ class _AddressesTab extends ConsumerWidget {
await ref.read(vendorDetailProvider(vendorId).notifier).deleteAddress(addressId);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
}
}
@ -482,7 +483,7 @@ class _ContactsTab extends ConsumerWidget {
vendorId: vendorId,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Contact saved')),
);
}
@ -550,7 +551,7 @@ class _ContactsTab extends ConsumerWidget {
await ref.read(vendorDetailProvider(vendorId).notifier).deleteContact(contactId);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
}
}
@ -582,7 +583,7 @@ class _BankDetailsTab extends ConsumerWidget {
vendorId: vendorId,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Bank detail saved')),
);
}
@ -652,7 +653,7 @@ class _BankDetailsTab extends ConsumerWidget {
.deleteBankDetail(bankDetailId);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
}
}

View File

@ -25,6 +25,7 @@ import '../../../../shared/widgets/error_view.dart';
import '../../../../shared/widgets/page_header.dart';
import '../providers/vendors_provider.dart';
import '../widgets/vendor_form_panel.dart';
import '../../../../shared/widgets/app_toast.dart';
class VendorListScreen extends ConsumerStatefulWidget {
const VendorListScreen({super.key});
@ -52,10 +53,10 @@ class _VendorListScreenState extends ConsumerState<VendorListScreen> {
final error = next.valueOrNull?.actionError;
final success = next.valueOrNull?.actionSuccess;
if (error != null && error != prev?.valueOrNull?.actionError) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
}
if (success != null && success != prev?.valueOrNull?.actionSuccess) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(success)));
showAppToastFromSnackBar(context, SnackBar(content: Text(success)));
}
});

View File

@ -16,6 +16,7 @@ import '../../../masters/data/datasources/master_remote_data_source.dart';
import '../../../master_data/presentation/widgets/master_quick_add.dart';
import '../../data/repositories/vendor_repository_impl.dart';
import '../providers/vendors_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
Future<void> openVendorFormPanel(
BuildContext context,
@ -29,7 +30,7 @@ Future<void> openVendorFormPanel(
width: 560,
);
if (saved == true && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(
content: Text(
vendorId == null
@ -136,7 +137,7 @@ class _VendorFormPanelState extends ConsumerState<VendorFormPanel> {
Future<void> _submit() async {
if (!_formKey.currentState!.validate()) return;
if (_vendorType == null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please select vendor type')),
);
return;
@ -154,7 +155,7 @@ class _VendorFormPanelState extends ConsumerState<VendorFormPanel> {
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(e.toString())),
);
}

View File

@ -8,6 +8,7 @@ import '../../../../shared/widgets/app_dropdown.dart';
import '../../../../shared/widgets/app_side_panel.dart';
import '../../../../shared/widgets/app_text_field.dart';
import '../providers/vendors_provider.dart';
import '../../../../shared/widgets/app_toast.dart';
Future<bool?> openVendorAddressPanel(
BuildContext context, {
@ -102,7 +103,7 @@ class _VendorAddressPanelState extends ConsumerState<VendorAddressPanel> {
Future<void> _save() async {
if (!_formKey.currentState!.validate()) return;
if (_addressType == null) {
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Please select address type')),
);
return;
@ -137,7 +138,7 @@ class _VendorAddressPanelState extends ConsumerState<VendorAddressPanel> {
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isSubmitting = false);
@ -279,7 +280,7 @@ class _VendorContactPanelState extends ConsumerState<VendorContactPanel> {
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isSubmitting = false);
@ -427,7 +428,7 @@ class _VendorBankDetailPanelState extends ConsumerState<VendorBankDetailPanel> {
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
}
} finally {
if (mounted) setState(() => _isSubmitting = false);

View File

@ -1,8 +1,14 @@
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
/// Saves [bytes] as a file download (web: same-tab download, desktop: save dialog).
///
/// Returns `true` when the download was started / the file was saved.
/// On web, [FilePicker.saveFile] always returns `null` after triggering the
/// browser download (no filesystem path), so success is assumed when the call
/// completes without error.
Future<bool> downloadFile({
required List<int> bytes,
required String fileName,
@ -11,5 +17,6 @@ Future<bool> downloadFile({
fileName: fileName,
bytes: Uint8List.fromList(bytes),
);
if (kIsWeb) return true;
return path != null;
}

View File

@ -3,22 +3,13 @@ import 'package:flutter/material.dart';
import '../../core/errors/failure.dart';
import '../../core/network/api_handler.dart';
import 'app_side_panel.dart';
import 'app_toast.dart';
void showAccessDeniedSnackBar(BuildContext context, {String? message}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.block, color: Colors.white, size: 18),
const SizedBox(width: 8),
Expanded(
child: Text(message ?? 'Access denied'),
),
],
),
backgroundColor: const Color(0xFFDC2626),
behavior: SnackBarBehavior.floating,
),
showAppToast(
context,
message ?? 'Access denied',
type: AppToastType.error,
);
}
@ -38,19 +29,15 @@ void showApiFailureSnackBar(BuildContext context, Failure failure) {
}
if (isConflictFailure(failure)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(failure.message)),
);
showAppToast(context, failure.message, type: AppToastType.warning);
return;
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
failure is ValidationFailure
? validationErrorMessage(failure)
: failure.message,
),
),
showAppToast(
context,
failure is ValidationFailure
? validationErrorMessage(failure)
: failure.message,
type: AppToastType.error,
);
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import '../../core/utils/responsive_utils.dart';
import 'app_toast.dart';
/// Holds a full-width Quick Add form below a form row (so it stays clickable).
class QuickAddInlineController extends ChangeNotifier {
@ -151,13 +152,13 @@ void showSidePanelSnackBar(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 4),
AppToastType? type,
}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
behavior: SnackBarBehavior.floating,
duration: duration,
),
showAppToast(
context,
message,
type: type ?? inferToastType(message),
duration: duration,
);
}

View File

@ -9,7 +9,6 @@ import '../../core/constants/enums.dart';
import '../../core/constants/route_constants.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/theme_provider.dart';
import '../../core/theme/theme_provider.dart';
import '../../core/utils/favicon_store.dart';
import '../../modules/settings/presentation/providers/settings_provider.dart';
import '../models/user_model.dart';

View File

@ -0,0 +1,485 @@
import 'package:flutter/material.dart';
/// Semantic toast variants with matching icons.
enum AppToastType {
success,
error,
warning,
info,
}
class _ToastEntry {
_ToastEntry({
required this.id,
required this.message,
required this.type,
required this.duration,
});
final String id;
final String message;
final AppToastType type;
final Duration duration;
}
/// Global controller for stacked top-center toasts.
class AppToastController extends ChangeNotifier {
AppToastController._();
static final AppToastController instance = AppToastController._();
final List<_ToastEntry> _entries = [];
int _seq = 0;
String show({
required String message,
AppToastType type = AppToastType.info,
Duration duration = const Duration(seconds: 4),
}) {
final trimmed = message.trim();
if (trimmed.isEmpty) return '';
final id = 'toast_${++_seq}_${DateTime.now().microsecondsSinceEpoch}';
_entries.add(
_ToastEntry(
id: id,
message: trimmed,
type: type,
duration: duration,
),
);
notifyListeners();
return id;
}
void dismiss(String id) {
final index = _entries.indexWhere((e) => e.id == id);
if (index < 0) return;
_entries.removeAt(index);
notifyListeners();
}
void clear() {
_entries.clear();
notifyListeners();
}
}
/// Shows a premium toast at the top-center. Safe to call from any [context]
/// under [AppToastHost] (wired in [MaterialApp.builder]).
void showAppToast(
BuildContext context,
String message, {
AppToastType type = AppToastType.info,
Duration duration = const Duration(seconds: 4),
}) {
// Prefer overlay host; fall back to themed SnackBar if host is missing.
if (AppToastHost.of(context) != null || _toastHostMounted) {
AppToastController.instance.show(
message: message,
type: type,
duration: duration,
);
return;
}
final messenger = ScaffoldMessenger.maybeOf(context);
if (messenger == null) return;
messenger.showSnackBar(
_fallbackSnackBar(context, message, type: type, duration: duration),
);
}
void showSuccessToast(BuildContext context, String message) =>
showAppToast(context, message, type: AppToastType.success);
void showErrorToast(BuildContext context, String message) =>
showAppToast(context, message, type: AppToastType.error);
void showWarningToast(BuildContext context, String message) =>
showAppToast(context, message, type: AppToastType.warning);
void showInfoToast(BuildContext context, String message) =>
showAppToast(context, message, type: AppToastType.info);
bool _toastHostMounted = false;
/// Infers toast type from message / snackbar styling for migration helpers.
AppToastType inferToastType(
String message, {
Color? backgroundColor,
}) {
final lower = message.toLowerCase();
if (backgroundColor != null) {
final r = backgroundColor.r;
final g = backgroundColor.g;
final b = backgroundColor.b;
if (r > g && r > b && r > 0.5) return AppToastType.error;
if (g > r && g > b && g > 0.4) return AppToastType.success;
if (r > 0.6 && g > 0.4 && b < 0.4) return AppToastType.warning;
}
if (lower.contains('access denied') ||
lower.contains('forbidden') ||
lower.contains('failed') ||
lower.contains('error') ||
lower.contains('invalid') ||
lower.contains('required') ||
lower.contains('could not') ||
lower.contains('unable') ||
lower.contains('denied')) {
return AppToastType.error;
}
if (lower.contains('warning') || lower.contains('only posted')) {
return AppToastType.warning;
}
if (lower.contains('success') ||
lower.contains('saved') ||
lower.contains('updated') ||
lower.contains('created') ||
lower.contains('deleted') ||
lower.contains('uploaded') ||
lower.contains('downloaded') ||
lower.contains('submitted') ||
lower.contains('approved') ||
lower.contains('cancelled') ||
lower.contains('canceled') ||
lower.contains('sent')) {
return AppToastType.success;
}
return AppToastType.info;
}
/// Drop-in for legacy `ScaffoldMessenger.showSnackBar(SnackBar(...))` call sites.
void showAppToastFromSnackBar(BuildContext context, SnackBar snackBar) {
final message = _extractSnackBarMessage(snackBar.content) ?? 'Notification';
final type = inferToastType(
message,
backgroundColor: snackBar.backgroundColor,
);
showAppToast(
context,
message,
type: type,
duration: snackBar.duration,
);
}
String? _extractSnackBarMessage(Widget content) {
if (content is Text) return content.data;
if (content is RichText) {
return content.text.toPlainText();
}
if (content is Row) {
final texts = <String>[];
for (final child in content.children) {
final msg = _extractSnackBarMessage(child);
if (msg != null && msg.trim().isNotEmpty) texts.add(msg.trim());
}
if (texts.isNotEmpty) return texts.join(' ');
}
if (content is Expanded) {
return _extractSnackBarMessage(content.child);
}
if (content is Flexible) {
return _extractSnackBarMessage(content.child);
}
if (content is Padding) {
final child = content.child;
if (child != null) return _extractSnackBarMessage(child);
}
if (content is Center) {
final child = content.child;
if (child != null) return _extractSnackBarMessage(child);
}
return null;
}
SnackBar _fallbackSnackBar(
BuildContext context,
String message, {
required AppToastType type,
required Duration duration,
}) {
final scheme = Theme.of(context).colorScheme;
return SnackBar(
content: Text(message),
behavior: SnackBarBehavior.floating,
duration: duration,
backgroundColor: switch (type) {
AppToastType.success => scheme.primary,
AppToastType.error => scheme.error,
AppToastType.warning => scheme.tertiary,
AppToastType.info => scheme.secondary,
},
);
}
/// Overlays stacked toasts at the top-center of the app.
class AppToastHost extends StatefulWidget {
const AppToastHost({super.key, required this.child});
final Widget child;
static AppToastHost? of(BuildContext context) {
return context.findAncestorWidgetOfExactType<AppToastHost>();
}
@override
State<AppToastHost> createState() => _AppToastHostState();
}
class _AppToastHostState extends State<AppToastHost> {
@override
void initState() {
super.initState();
_toastHostMounted = true;
AppToastController.instance.addListener(_onChange);
}
@override
void dispose() {
AppToastController.instance.removeListener(_onChange);
_toastHostMounted = false;
super.dispose();
}
void _onChange() {
if (mounted) setState(() {});
}
@override
Widget build(BuildContext context) {
final media = MediaQuery.of(context);
final topInset = media.padding.top + 12;
final maxWidth = media.size.width < 600
? media.size.width - 24
: media.size.width < 1024
? 420.0
: 480.0;
final entries = List<_ToastEntry>.from(AppToastController.instance._entries);
return Stack(
children: [
widget.child,
Positioned(
top: topInset,
left: 0,
right: 0,
child: IgnorePointer(
ignoring: entries.isEmpty,
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (var i = 0; i < entries.length; i++) ...[
if (i > 0) const SizedBox(height: 8),
_ToastCard(
key: ValueKey(entries[i].id),
entry: entries[i],
onClose: () =>
AppToastController.instance.dismiss(entries[i].id),
),
],
],
),
),
),
),
),
),
],
);
}
}
class _ToastCard extends StatefulWidget {
const _ToastCard({
super.key,
required this.entry,
required this.onClose,
});
final _ToastEntry entry;
final VoidCallback onClose;
@override
State<_ToastCard> createState() => _ToastCardState();
}
class _ToastCardState extends State<_ToastCard>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _fade;
late final Animation<Offset> _slide;
bool _closing = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 280),
reverseDuration: const Duration(milliseconds: 220),
);
_fade = CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic);
_slide = Tween<Offset>(
begin: const Offset(0, -0.35),
end: Offset.zero,
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic));
_controller.forward();
Future<void>.delayed(widget.entry.duration, () {
if (mounted && !_closing) _close();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _close() async {
if (_closing) return;
_closing = true;
await _controller.reverse();
if (mounted) widget.onClose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final tokens = _ToastTokens.from(scheme, widget.entry.type);
return SlideTransition(
position: _slide,
child: FadeTransition(
opacity: _fade,
child: Material(
color: scheme.surface.withValues(alpha: 0),
child: DecoratedBox(
decoration: BoxDecoration(
color: tokens.background,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: tokens.border),
boxShadow: [
BoxShadow(
color: scheme.shadow.withValues(alpha: theme.brightness == Brightness.dark ? 0.45 : 0.14),
blurRadius: 18,
offset: const Offset(0, 8),
),
],
),
child: Padding(
padding: const EdgeInsets.fromLTRB(14, 12, 6, 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: tokens.iconBackground,
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: Icon(tokens.icon, size: 18, color: tokens.iconColor),
),
const SizedBox(width: 12),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 5),
child: Text(
widget.entry.message,
style: theme.textTheme.bodyMedium?.copyWith(
color: tokens.foreground,
height: 1.35,
fontWeight: FontWeight.w500,
),
),
),
),
const SizedBox(width: 4),
Semantics(
button: true,
label: 'Dismiss',
child: InkWell(
onTap: _close,
customBorder: const CircleBorder(),
child: SizedBox(
width: 36,
height: 36,
child: Icon(
Icons.close_rounded,
size: 18,
color: tokens.foreground.withValues(alpha: 0.65),
),
),
),
),
],
),
),
),
),
),
);
}
}
class _ToastTokens {
const _ToastTokens({
required this.background,
required this.border,
required this.foreground,
required this.icon,
required this.iconColor,
required this.iconBackground,
});
final Color background;
final Color border;
final Color foreground;
final IconData icon;
final Color iconColor;
final Color iconBackground;
factory _ToastTokens.from(ColorScheme scheme, AppToastType type) {
final accent = switch (type) {
AppToastType.success => scheme.primary,
AppToastType.error => scheme.error,
AppToastType.warning => scheme.tertiary,
AppToastType.info => scheme.secondary,
};
final icon = switch (type) {
AppToastType.success => Icons.check_circle_rounded,
AppToastType.error => Icons.cancel_rounded,
AppToastType.warning => Icons.warning_amber_rounded,
AppToastType.info => Icons.info_rounded,
};
// Surface-based card so light/dark themes stay cohesive.
final background = Color.alphaBlend(
accent.withValues(alpha: 0.08),
scheme.surface,
);
final border = accent.withValues(alpha: 0.28);
final foreground = scheme.onSurface;
final iconBackground = accent.withValues(alpha: 0.14);
return _ToastTokens(
background: background,
border: border,
foreground: foreground,
icon: icon,
iconColor: accent,
iconBackground: iconBackground,
);
}
}

View File

@ -7,6 +7,7 @@ import '../../core/utils/formatters.dart';
import '../models/entity_attachment_model.dart';
import '../utils/file_download_helper.dart';
import 'app_confirmation_dialog.dart';
import 'app_toast.dart';
const kEntityAttachmentExtensions = ['pdf', 'jpg', 'jpeg', 'png', 'webp'];
@ -80,7 +81,7 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
final bytes = file.bytes;
if (bytes == null || bytes.isEmpty) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Could not read the selected file')),
);
return;
@ -89,7 +90,7 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
final ext = (file.extension ?? '').toLowerCase();
if (!kEntityAttachmentExtensions.contains(ext)) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(
content: Text('Only PDF, JPEG, PNG, and WebP files are allowed'),
),
@ -101,14 +102,14 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
try {
await widget.onUpload(bytes: bytes, filename: file.name);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text('${file.name} uploaded')),
);
} catch (e) {
if (!mounted) return;
final message =
e is Failure ? validationErrorMessage(e) : e.toString();
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(message)),
);
} finally {
@ -127,7 +128,7 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(e.toString())),
);
} finally {
@ -152,14 +153,14 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
try {
await widget.onDelete(attachment.id);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
const SnackBar(content: Text('Attachment deleted')),
);
} catch (e) {
if (!mounted) return;
final message =
e is Failure ? validationErrorMessage(e) : e.toString();
ScaffoldMessenger.of(context).showSnackBar(
showAppToastFromSnackBar(context,
SnackBar(content: Text(message)),
);
} finally {