toast
This commit is contained in:
parent
a4634a9ba2
commit
afe9a0736d
19
lib/app.dart
19
lib/app.dart
@ -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'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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.'),
|
||||
),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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())),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 ??
|
||||
|
||||
@ -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()}',
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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'),
|
||||
),
|
||||
|
||||
@ -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: [
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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 ??
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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'),
|
||||
),
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
},
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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'),
|
||||
),
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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())),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
485
lib/shared/widgets/app_toast.dart
Normal file
485
lib/shared/widgets/app_toast.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user