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/constants/app_constants.dart';
|
||||||
import 'core/theme/theme_provider.dart';
|
import 'core/theme/theme_provider.dart';
|
||||||
import 'shared/routes/app_router.dart';
|
import 'shared/routes/app_router.dart';
|
||||||
|
import 'shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class BharatErpApp extends ConsumerWidget {
|
class BharatErpApp extends ConsumerWidget {
|
||||||
const BharatErpApp({super.key});
|
const BharatErpApp({super.key});
|
||||||
@ -23,14 +24,16 @@ class BharatErpApp extends ConsumerWidget {
|
|||||||
themeMode: resolveThemeMode(themeMode),
|
themeMode: resolveThemeMode(themeMode),
|
||||||
themeAnimationDuration: Duration.zero,
|
themeAnimationDuration: Duration.zero,
|
||||||
routerConfig: router,
|
routerConfig: router,
|
||||||
builder: (context, child) => ResponsiveBreakpoints.builder(
|
builder: (context, child) => AppToastHost(
|
||||||
child: child!,
|
child: ResponsiveBreakpoints.builder(
|
||||||
breakpoints: const [
|
child: child ?? const SizedBox.shrink(),
|
||||||
Breakpoint(start: 0, end: 599, name: MOBILE),
|
breakpoints: const [
|
||||||
Breakpoint(start: 600, end: 1023, name: TABLET),
|
Breakpoint(start: 0, end: 599, name: MOBILE),
|
||||||
Breakpoint(start: 1024, end: 1439, name: DESKTOP),
|
Breakpoint(start: 600, end: 1023, name: TABLET),
|
||||||
Breakpoint(start: 1440, end: double.infinity, name: '4K'),
|
Breakpoint(start: 1024, end: 1439, name: DESKTOP),
|
||||||
],
|
Breakpoint(start: 1440, end: double.infinity, name: '4K'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -196,8 +196,13 @@ class AppTheme {
|
|||||||
),
|
),
|
||||||
snackBarTheme: SnackBarThemeData(
|
snackBarTheme: SnackBarThemeData(
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
contentTextStyle: textTheme.bodyMedium?.copyWith(color: colorScheme.onInverseSurface),
|
elevation: 0,
|
||||||
|
backgroundColor: colorScheme.inverseSurface,
|
||||||
|
contentTextStyle: textTheme.bodyMedium?.copyWith(
|
||||||
|
color: colorScheme.onInverseSurface,
|
||||||
|
),
|
||||||
|
insetPadding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
|
||||||
),
|
),
|
||||||
dataTableTheme: DataTableThemeData(
|
dataTableTheme: DataTableThemeData(
|
||||||
headingRowColor: WidgetStateProperty.all(
|
headingRowColor: WidgetStateProperty.all(
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import '../providers/assets_provider.dart';
|
|||||||
import '../providers/asset_form_lookups_provider.dart';
|
import '../providers/asset_form_lookups_provider.dart';
|
||||||
import '../widgets/asset_form_panel.dart';
|
import '../widgets/asset_form_panel.dart';
|
||||||
import '../widgets/asset_side_panels.dart';
|
import '../widgets/asset_side_panels.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class AssetDetailScreen extends ConsumerStatefulWidget {
|
class AssetDetailScreen extends ConsumerStatefulWidget {
|
||||||
const AssetDetailScreen({super.key, required this.assetId});
|
const AssetDetailScreen({super.key, required this.assetId});
|
||||||
@ -161,7 +162,7 @@ class _AssetDetailScreenState extends ConsumerState<AssetDetailScreen>
|
|||||||
width: 520,
|
width: 520,
|
||||||
);
|
);
|
||||||
if (transferred == true && mounted) {
|
if (transferred == true && mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Asset transferred successfully')),
|
const SnackBar(content: Text('Asset transferred successfully')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -476,7 +477,7 @@ class _AmcTab extends ConsumerWidget {
|
|||||||
width: 520,
|
width: 520,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('AMC contract created')),
|
const SnackBar(content: Text('AMC contract created')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -493,7 +494,7 @@ class _AmcTab extends ConsumerWidget {
|
|||||||
width: 520,
|
width: 520,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('AMC contract updated')),
|
const SnackBar(content: Text('AMC contract updated')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -571,7 +572,7 @@ class _ServiceVisitsTab extends ConsumerWidget {
|
|||||||
width: 560,
|
width: 560,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Service visit logged')),
|
const SnackBar(content: Text('Service visit logged')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -592,7 +593,7 @@ class _ServiceVisitsTab extends ConsumerWidget {
|
|||||||
width: 560,
|
width: 560,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Service visit updated')),
|
const SnackBar(content: Text('Service visit updated')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -662,7 +663,7 @@ class _InsuranceTab extends ConsumerWidget {
|
|||||||
width: 520,
|
width: 520,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Insurance policy created')),
|
const SnackBar(content: Text('Insurance policy created')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -679,7 +680,7 @@ class _InsuranceTab extends ConsumerWidget {
|
|||||||
width: 520,
|
width: 520,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Insurance policy updated')),
|
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/asset_form_lookups_provider.dart';
|
||||||
import '../providers/assets_provider.dart';
|
import '../providers/assets_provider.dart';
|
||||||
import '../widgets/asset_form_panel.dart';
|
import '../widgets/asset_form_panel.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class AssetListScreen extends ConsumerStatefulWidget {
|
class AssetListScreen extends ConsumerStatefulWidget {
|
||||||
const AssetListScreen({super.key});
|
const AssetListScreen({super.key});
|
||||||
@ -49,10 +50,10 @@ class _AssetListScreenState extends ConsumerState<AssetListScreen> {
|
|||||||
final error = next.valueOrNull?.actionError;
|
final error = next.valueOrNull?.actionError;
|
||||||
final success = next.valueOrNull?.actionSuccess;
|
final success = next.valueOrNull?.actionSuccess;
|
||||||
if (error != null && error != prev?.valueOrNull?.actionError) {
|
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) {
|
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 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
|
||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class AssetQrGenerateScreen extends StatelessWidget {
|
class AssetQrGenerateScreen extends StatelessWidget {
|
||||||
const AssetQrGenerateScreen({super.key});
|
const AssetQrGenerateScreen({super.key});
|
||||||
@ -32,7 +33,7 @@ class AssetQrGenerateScreen extends StatelessWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Print command hooked for platform print service.'),
|
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_categories_provider.dart';
|
||||||
import '../providers/asset_form_lookups_provider.dart';
|
import '../providers/asset_form_lookups_provider.dart';
|
||||||
import '../providers/assets_provider.dart';
|
import '../providers/assets_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
Future<void> openAssetFormPanel(
|
Future<void> openAssetFormPanel(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
@ -37,7 +38,7 @@ Future<void> openAssetFormPanel(
|
|||||||
width: panelWidth,
|
width: panelWidth,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
assetId == null
|
assetId == null
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import '../../../../shared/widgets/error_view.dart';
|
|||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
import '../providers/audit_provider.dart';
|
import '../providers/audit_provider.dart';
|
||||||
import '../widgets/audit_log_detail_panel.dart';
|
import '../widgets/audit_log_detail_panel.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class AuditLogsScreen extends ConsumerStatefulWidget {
|
class AuditLogsScreen extends ConsumerStatefulWidget {
|
||||||
const AuditLogsScreen({super.key});
|
const AuditLogsScreen({super.key});
|
||||||
@ -49,7 +50,7 @@ class _AuditLogsScreenState extends ConsumerState<AuditLogsScreen> {
|
|||||||
if (file == null) {
|
if (file == null) {
|
||||||
final error = ref.read(auditLogsListProvider).valueOrNull?.actionError;
|
final error = ref.read(auditLogsListProvider).valueOrNull?.actionError;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error)));
|
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -60,7 +61,7 @@ class _AuditLogsScreenState extends ConsumerState<AuditLogsScreen> {
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(saved ? 'Downloaded ${file.fileName}' : 'Export cancelled'),
|
content: Text(saved ? 'Downloaded ${file.fileName}' : 'Export cancelled'),
|
||||||
),
|
),
|
||||||
@ -111,7 +112,7 @@ class _AuditLogsScreenState extends ConsumerState<AuditLogsScreen> {
|
|||||||
ref.listen(auditLogsListProvider, (prev, next) {
|
ref.listen(auditLogsListProvider, (prev, next) {
|
||||||
final error = next.valueOrNull?.actionError;
|
final error = next.valueOrNull?.actionError;
|
||||||
if (error != null && error != prev?.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 '../../../../core/utils/validators.dart';
|
||||||
import '../../../../shared/widgets/app_button.dart';
|
import '../../../../shared/widgets/app_button.dart';
|
||||||
import '../../../../shared/widgets/app_text_field.dart';
|
import '../../../../shared/widgets/app_text_field.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class ChangePasswordScreen extends StatefulWidget {
|
class ChangePasswordScreen extends StatefulWidget {
|
||||||
const ChangePasswordScreen({super.key});
|
const ChangePasswordScreen({super.key});
|
||||||
@ -66,7 +67,7 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
|||||||
label: 'Update Password',
|
label: 'Update Password',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Password updated (API pending)')),
|
const SnackBar(content: Text('Password updated (API pending)')),
|
||||||
);
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import '../../../../shared/widgets/app_button.dart';
|
|||||||
import '../widgets/bcpl_logo.dart';
|
import '../widgets/bcpl_logo.dart';
|
||||||
import '../widgets/login_colors.dart';
|
import '../widgets/login_colors.dart';
|
||||||
import '../widgets/login_hero_panel.dart';
|
import '../widgets/login_hero_panel.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class LoginScreen extends ConsumerStatefulWidget {
|
class LoginScreen extends ConsumerStatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
@ -87,7 +88,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||||||
context.go(RouteConstants.dashboard);
|
context.go(RouteConstants.dashboard);
|
||||||
} else {
|
} else {
|
||||||
final error = ref.read(authStateProvider).error;
|
final error = ref.read(authStateProvider).error;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(error ?? 'Invalid credentials')),
|
SnackBar(content: Text(error ?? 'Invalid credentials')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import '../../../../core/constants/route_constants.dart';
|
|||||||
import '../../../../core/utils/validators.dart';
|
import '../../../../core/utils/validators.dart';
|
||||||
import '../../../../shared/widgets/app_button.dart';
|
import '../../../../shared/widgets/app_button.dart';
|
||||||
import '../../../../shared/widgets/app_text_field.dart';
|
import '../../../../shared/widgets/app_text_field.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class ResetPasswordScreen extends StatefulWidget {
|
class ResetPasswordScreen extends StatefulWidget {
|
||||||
const ResetPasswordScreen({super.key});
|
const ResetPasswordScreen({super.key});
|
||||||
@ -59,7 +60,7 @@ class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
|
|||||||
label: 'Reset Password',
|
label: 'Reset Password',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Password reset (API token flow pending)')),
|
const SnackBar(content: Text('Password reset (API token flow pending)')),
|
||||||
);
|
);
|
||||||
context.go(RouteConstants.login);
|
context.go(RouteConstants.login);
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import '../providers/grn_provider.dart';
|
|||||||
import '../widgets/grn_attachments_card.dart';
|
import '../widgets/grn_attachments_card.dart';
|
||||||
import '../widgets/grn_line_items_editor.dart';
|
import '../widgets/grn_line_items_editor.dart';
|
||||||
import '../widgets/grn_status_chip.dart';
|
import '../widgets/grn_status_chip.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class GrnDetailScreen extends ConsumerStatefulWidget {
|
class GrnDetailScreen extends ConsumerStatefulWidget {
|
||||||
const GrnDetailScreen({super.key, required this.grnId});
|
const GrnDetailScreen({super.key, required this.grnId});
|
||||||
@ -116,13 +117,11 @@ class _GrnDetailScreenState extends ConsumerState<GrnDetailScreen> {
|
|||||||
try {
|
try {
|
||||||
await action();
|
await action();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(success)));
|
||||||
.showSnackBar(SnackBar(content: Text(success)));
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
.showSnackBar(SnackBar(content: Text(e.toString())));
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isWorking = false);
|
if (mounted) setState(() => _isWorking = false);
|
||||||
@ -186,13 +185,11 @@ class _GrnDetailScreenState extends ConsumerState<GrnDetailScreen> {
|
|||||||
fileName: '${grn.grnNumber ?? 'GRN-${grn.id}'}.pdf',
|
fileName: '${grn.grnNumber ?? 'GRN-${grn.id}'}.pdf',
|
||||||
);
|
);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, const SnackBar(content: Text('PDF downloaded')));
|
||||||
.showSnackBar(const SnackBar(content: Text('PDF downloaded')));
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
.showSnackBar(SnackBar(content: Text(e.toString())));
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isDownloadingPdf = false);
|
if (mounted) setState(() => _isDownloadingPdf = false);
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import '../providers/grn_lookups_provider.dart';
|
|||||||
import '../providers/grn_provider.dart';
|
import '../providers/grn_provider.dart';
|
||||||
import '../widgets/grn_line_items_editor.dart';
|
import '../widgets/grn_line_items_editor.dart';
|
||||||
import '../widgets/grn_status_chip.dart';
|
import '../widgets/grn_status_chip.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class GrnFormScreen extends ConsumerStatefulWidget {
|
class GrnFormScreen extends ConsumerStatefulWidget {
|
||||||
const GrnFormScreen({super.key, this.grnId});
|
const GrnFormScreen({super.key, this.grnId});
|
||||||
@ -226,7 +227,7 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
|
|||||||
if (formState == null) return;
|
if (formState == null) return;
|
||||||
|
|
||||||
if (!formState.validate()) {
|
if (!formState.validate()) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Please fix the highlighted errors before saving'),
|
content: Text('Please fix the highlighted errors before saving'),
|
||||||
),
|
),
|
||||||
@ -236,15 +237,14 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
|
|||||||
|
|
||||||
if (!widget.isEditing) {
|
if (!widget.isEditing) {
|
||||||
if (_selectedPoId == null || _warehouseId == null) {
|
if (_selectedPoId == null || _warehouseId == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Please select PO and warehouse')),
|
const SnackBar(content: Text('Please select PO and warehouse')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final lineError = _lineItemsError();
|
final lineError = _lineItemsError();
|
||||||
if (lineError != null) {
|
if (lineError != null) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(lineError)));
|
||||||
.showSnackBar(SnackBar(content: Text(lineError)));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(widget.isEditing ? 'GRN updated' : 'GRN created'),
|
content: Text(widget.isEditing ? 'GRN updated' : 'GRN created'),
|
||||||
),
|
),
|
||||||
@ -279,8 +279,7 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final message =
|
final message =
|
||||||
e is Failure ? validationErrorMessage(e) : e.toString();
|
e is Failure ? validationErrorMessage(e) : e.toString();
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(message)));
|
||||||
.showSnackBar(SnackBar(content: Text(message)));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isSubmitting = false);
|
if (mounted) setState(() => _isSubmitting = false);
|
||||||
}
|
}
|
||||||
@ -443,7 +442,7 @@ class _GrnFormScreenState extends ConsumerState<GrnFormScreen> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(e.toString())),
|
SnackBar(content: Text(e.toString())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import '../../../../shared/widgets/error_view.dart';
|
|||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
import '../providers/grn_provider.dart';
|
import '../providers/grn_provider.dart';
|
||||||
import '../widgets/grn_status_chip.dart';
|
import '../widgets/grn_status_chip.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class GrnListScreen extends ConsumerStatefulWidget {
|
class GrnListScreen extends ConsumerStatefulWidget {
|
||||||
const GrnListScreen({super.key});
|
const GrnListScreen({super.key});
|
||||||
@ -137,7 +138,7 @@ class _GrnListScreenState extends ConsumerState<GrnListScreen> {
|
|||||||
|
|
||||||
void _editGrn(GrnModel grn) {
|
void _editGrn(GrnModel grn) {
|
||||||
if (!grn.canEdit) {
|
if (!grn.canEdit) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Only posted GRNs can be edited')),
|
const SnackBar(content: Text('Only posted GRNs can be edited')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import '../../../../shared/models/grn_model.dart';
|
|||||||
import '../../../../shared/utils/file_download_helper.dart';
|
import '../../../../shared/utils/file_download_helper.dart';
|
||||||
import '../../../../shared/widgets/app_confirmation_dialog.dart';
|
import '../../../../shared/widgets/app_confirmation_dialog.dart';
|
||||||
import '../providers/grn_provider.dart';
|
import '../providers/grn_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
const _allowedExtensions = ['pdf', 'jpg', 'jpeg', 'png', 'webp'];
|
const _allowedExtensions = ['pdf', 'jpg', 'jpeg', 'png', 'webp'];
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
|
|||||||
final bytes = file.bytes;
|
final bytes = file.bytes;
|
||||||
if (bytes == null || bytes.isEmpty) {
|
if (bytes == null || bytes.isEmpty) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Could not read the selected file')),
|
const SnackBar(content: Text('Could not read the selected file')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -74,7 +75,7 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
|
|||||||
final ext = (file.extension ?? '').toLowerCase();
|
final ext = (file.extension ?? '').toLowerCase();
|
||||||
if (!_allowedExtensions.contains(ext)) {
|
if (!_allowedExtensions.contains(ext)) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Only PDF, JPEG, PNG, and WebP files are allowed'),
|
content: Text('Only PDF, JPEG, PNG, and WebP files are allowed'),
|
||||||
),
|
),
|
||||||
@ -89,14 +90,14 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
|
|||||||
filename: file.name,
|
filename: file.name,
|
||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text('${file.name} uploaded')),
|
SnackBar(content: Text('${file.name} uploaded')),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final message =
|
final message =
|
||||||
e is Failure ? validationErrorMessage(e) : e.toString();
|
e is Failure ? validationErrorMessage(e) : e.toString();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(message)),
|
SnackBar(content: Text(message)),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@ -117,7 +118,7 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(e.toString())),
|
SnackBar(content: Text(e.toString())),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@ -144,14 +145,14 @@ class _GrnAttachmentsCardState extends ConsumerState<GrnAttachmentsCard> {
|
|||||||
.read(grnDetailProvider(widget.grn.id).notifier)
|
.read(grnDetailProvider(widget.grn.id).notifier)
|
||||||
.deleteAttachment(attachment.id);
|
.deleteAttachment(attachment.id);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Attachment deleted')),
|
const SnackBar(content: Text('Attachment deleted')),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final message =
|
final message =
|
||||||
e is Failure ? validationErrorMessage(e) : e.toString();
|
e is Failure ? validationErrorMessage(e) : e.toString();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(message)),
|
SnackBar(content: Text(message)),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import '../../../../shared/widgets/page_header.dart';
|
|||||||
import '../../domain/entities/master_definition.dart';
|
import '../../domain/entities/master_definition.dart';
|
||||||
import '../providers/master_provider.dart';
|
import '../providers/master_provider.dart';
|
||||||
import '../widgets/master_form_panel.dart';
|
import '../widgets/master_form_panel.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class MasterListScreen extends ConsumerStatefulWidget {
|
class MasterListScreen extends ConsumerStatefulWidget {
|
||||||
const MasterListScreen({super.key, required this.masterId});
|
const MasterListScreen({super.key, required this.masterId});
|
||||||
@ -63,7 +64,7 @@ class _MasterListScreenState extends ConsumerState<MasterListScreen> {
|
|||||||
if (file == null) {
|
if (file == null) {
|
||||||
final error = ref.read(masterListProvider(widget.masterId)).valueOrNull?.actionError;
|
final error = ref.read(masterListProvider(widget.masterId)).valueOrNull?.actionError;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(error)),
|
SnackBar(content: Text(error)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -76,7 +77,7 @@ class _MasterListScreenState extends ConsumerState<MasterListScreen> {
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
saved ? 'Downloaded ${file.fileName}' : 'Export cancelled',
|
saved ? 'Downloaded ${file.fileName}' : 'Export cancelled',
|
||||||
@ -105,7 +106,7 @@ class _MasterListScreenState extends ConsumerState<MasterListScreen> {
|
|||||||
);
|
);
|
||||||
if (saved != null && mounted) {
|
if (saved != null && mounted) {
|
||||||
ref.invalidate(masterListProvider(widget.masterId));
|
ref.invalidate(masterListProvider(widget.masterId));
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
recordId == null
|
recordId == null
|
||||||
@ -134,7 +135,7 @@ class _MasterListScreenState extends ConsumerState<MasterListScreen> {
|
|||||||
await ref.read(masterListProvider(widget.masterId).notifier).deleteRecord(id);
|
await ref.read(masterListProvider(widget.masterId).notifier).deleteRecord(id);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
success
|
success
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import '../../../../shared/widgets/error_view.dart';
|
|||||||
import '../../domain/entities/master_definition.dart';
|
import '../../domain/entities/master_definition.dart';
|
||||||
import '../providers/master_provider.dart';
|
import '../providers/master_provider.dart';
|
||||||
import 'master_quick_add.dart';
|
import 'master_quick_add.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class MasterFormPanel extends ConsumerStatefulWidget {
|
class MasterFormPanel extends ConsumerStatefulWidget {
|
||||||
const MasterFormPanel({
|
const MasterFormPanel({
|
||||||
@ -69,7 +70,7 @@ class _MasterFormPanelState extends ConsumerState<MasterFormPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final error = ref.read(masterFormProvider(_args)).valueOrNull?.errorMessage;
|
final error = ref.read(masterFormProvider(_args)).valueOrNull?.errorMessage;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
error ??
|
error ??
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import '../../../../shared/widgets/app_searchable_dropdown.dart';
|
|||||||
import '../../../../shared/widgets/error_view.dart';
|
import '../../../../shared/widgets/error_view.dart';
|
||||||
import '../../domain/entities/master_definition.dart';
|
import '../../domain/entities/master_definition.dart';
|
||||||
import '../providers/master_provider.dart';
|
import '../providers/master_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
/// Compact create form for inline Quick Add (no side panel / dialog).
|
/// Compact create form for inline Quick Add (no side panel / dialog).
|
||||||
class MasterInlineCreateForm extends ConsumerStatefulWidget {
|
class MasterInlineCreateForm extends ConsumerStatefulWidget {
|
||||||
@ -63,7 +64,7 @@ class _MasterInlineCreateFormState
|
|||||||
}
|
}
|
||||||
|
|
||||||
final error = ref.read(masterFormProvider(_args)).valueOrNull?.errorMessage;
|
final error = ref.read(masterFormProvider(_args)).valueOrNull?.errorMessage;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
error ?? 'Failed to create ${_definition.title.toLowerCase()}',
|
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 '../../../../shared/widgets/app_side_panel.dart';
|
||||||
import '../providers/master_provider.dart';
|
import '../providers/master_provider.dart';
|
||||||
import 'master_inline_create_form.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).
|
/// Dropdown with inline Quick Add that expands below the field (no dialog).
|
||||||
///
|
///
|
||||||
@ -72,7 +73,7 @@ class _MasterQuickAddDropdownState<T>
|
|||||||
|
|
||||||
void _openInlineForm() {
|
void _openInlineForm() {
|
||||||
if (!_canQuickAdd) {
|
if (!_canQuickAdd) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('You do not have permission to add master data'),
|
content: Text('You do not have permission to add master data'),
|
||||||
),
|
),
|
||||||
@ -126,7 +127,7 @@ class _MasterQuickAddDropdownState<T>
|
|||||||
_collapse();
|
_collapse();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content:
|
content:
|
||||||
Text('${_titleCase(masterQuickAddNoun(widget.masterId))} added'),
|
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 '../../data/repositories/purchase_order_repository_impl.dart';
|
||||||
import '../widgets/po_status_chip.dart';
|
import '../widgets/po_status_chip.dart';
|
||||||
import '../widgets/purchase_order_line_items_editor.dart';
|
import '../widgets/purchase_order_line_items_editor.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class PurchaseOrderDetailScreen extends ConsumerStatefulWidget {
|
class PurchaseOrderDetailScreen extends ConsumerStatefulWidget {
|
||||||
const PurchaseOrderDetailScreen({super.key, required this.purchaseOrderId});
|
const PurchaseOrderDetailScreen({super.key, required this.purchaseOrderId});
|
||||||
@ -145,13 +146,11 @@ class _PurchaseOrderDetailScreenState
|
|||||||
try {
|
try {
|
||||||
await action();
|
await action();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(success)));
|
||||||
.showSnackBar(SnackBar(content: Text(success)));
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
.showSnackBar(SnackBar(content: Text(e.toString())));
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isWorking = false);
|
if (mounted) setState(() => _isWorking = false);
|
||||||
@ -203,7 +202,7 @@ class _PurchaseOrderDetailScreenState
|
|||||||
final remarks = remarksController.text.trim();
|
final remarks = remarksController.text.trim();
|
||||||
remarksController.dispose();
|
remarksController.dispose();
|
||||||
if (remarks.isEmpty) {
|
if (remarks.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Rejection remarks are required')),
|
const SnackBar(content: Text('Rejection remarks are required')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -231,15 +230,14 @@ class _PurchaseOrderDetailScreenState
|
|||||||
.read(purchaseOrderDetailProvider(widget.purchaseOrderId).notifier)
|
.read(purchaseOrderDetailProvider(widget.purchaseOrderId).notifier)
|
||||||
.amend();
|
.amend();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Amendment draft created')),
|
const SnackBar(content: Text('Amendment draft created')),
|
||||||
);
|
);
|
||||||
context.go('${RouteConstants.purchaseOrders}/${amended.id}');
|
context.go('${RouteConstants.purchaseOrders}/${amended.id}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
.showSnackBar(SnackBar(content: Text(e.toString())));
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isWorking = false);
|
if (mounted) setState(() => _isWorking = false);
|
||||||
@ -281,15 +279,14 @@ class _PurchaseOrderDetailScreenState
|
|||||||
if (result.failure != null) throw result.failure!;
|
if (result.failure != null) throw result.failure!;
|
||||||
ref.invalidate(purchaseOrdersListProvider);
|
ref.invalidate(purchaseOrdersListProvider);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Purchase order deleted')),
|
const SnackBar(content: Text('Purchase order deleted')),
|
||||||
);
|
);
|
||||||
context.go(RouteConstants.purchaseOrders);
|
context.go(RouteConstants.purchaseOrders);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
.showSnackBar(SnackBar(content: Text(e.toString())));
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isWorking = false);
|
if (mounted) setState(() => _isWorking = false);
|
||||||
@ -309,8 +306,7 @@ class _PurchaseOrderDetailScreenState
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
.showSnackBar(SnackBar(content: Text(e.toString())));
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isDownloadingPdf = false);
|
if (mounted) setState(() => _isDownloadingPdf = false);
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import '../providers/purchase_order_lookups_provider.dart';
|
|||||||
import '../providers/purchase_orders_provider.dart';
|
import '../providers/purchase_orders_provider.dart';
|
||||||
import '../widgets/po_status_chip.dart';
|
import '../widgets/po_status_chip.dart';
|
||||||
import '../widgets/purchase_order_line_items_editor.dart';
|
import '../widgets/purchase_order_line_items_editor.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class PurchaseOrderFormScreen extends ConsumerStatefulWidget {
|
class PurchaseOrderFormScreen extends ConsumerStatefulWidget {
|
||||||
const PurchaseOrderFormScreen({super.key, this.purchaseOrderId});
|
const PurchaseOrderFormScreen({super.key, this.purchaseOrderId});
|
||||||
@ -235,7 +236,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
|
|||||||
|
|
||||||
final isValid = formState.validate();
|
final isValid = formState.validate();
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Please fix the highlighted errors before saving'),
|
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) {
|
if (_poType == null || _vendorId == null || _plantId == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Please complete all required fields')),
|
const SnackBar(content: Text('Please complete all required fields')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -252,14 +253,14 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
|
|||||||
|
|
||||||
final lineItemsError = _lineItemsError();
|
final lineItemsError = _lineItemsError();
|
||||||
if (lineItemsError != null) {
|
if (lineItemsError != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(lineItemsError)),
|
SnackBar(content: Text(lineItemsError)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_lines.isEmpty) {
|
if (_lines.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Add at least one line item')),
|
const SnackBar(content: Text('Add at least one line item')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -283,7 +284,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
widget.isEditing ? 'Purchase order updated' : 'Purchase order created',
|
widget.isEditing ? 'Purchase order updated' : 'Purchase order created',
|
||||||
@ -302,7 +303,7 @@ class _PurchaseOrderFormScreenState extends ConsumerState<PurchaseOrderFormScree
|
|||||||
final message = e is Failure
|
final message = e is Failure
|
||||||
? validationErrorMessage(e)
|
? validationErrorMessage(e)
|
||||||
: e.toString();
|
: e.toString();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(message)),
|
SnackBar(content: Text(message)),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import '../../../../shared/widgets/error_view.dart';
|
|||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
import '../providers/purchase_orders_provider.dart';
|
import '../providers/purchase_orders_provider.dart';
|
||||||
import '../widgets/po_status_chip.dart';
|
import '../widgets/po_status_chip.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class PurchaseOrderListScreen extends ConsumerStatefulWidget {
|
class PurchaseOrderListScreen extends ConsumerStatefulWidget {
|
||||||
const PurchaseOrderListScreen({super.key});
|
const PurchaseOrderListScreen({super.key});
|
||||||
@ -63,10 +64,10 @@ class _PurchaseOrderListScreenState extends ConsumerState<PurchaseOrderListScree
|
|||||||
final error = next.valueOrNull?.actionError;
|
final error = next.valueOrNull?.actionError;
|
||||||
final success = next.valueOrNull?.actionSuccess;
|
final success = next.valueOrNull?.actionSuccess;
|
||||||
if (error != null && error != prev?.valueOrNull?.actionError) {
|
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) {
|
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) {
|
void _editOrder(PurchaseOrderModel order) {
|
||||||
if (!order.canEdit) {
|
if (!order.canEdit) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Only draft or rejected orders can be edited'),
|
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 {
|
Future<void> _deleteOrder(PurchaseOrderModel order) async {
|
||||||
if (!order.canDelete) {
|
if (!order.canDelete) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Only draft or rejected orders can be deleted'),
|
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/add_user_panel.dart';
|
||||||
import '../widgets/create_role_panel.dart';
|
import '../widgets/create_role_panel.dart';
|
||||||
import '../widgets/rbac_widgets.dart';
|
import '../widgets/rbac_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
RbacTab rbacTabFromLocation(String location) {
|
RbacTab rbacTabFromLocation(String location) {
|
||||||
if (location.startsWith(RouteConstants.roles)) return RbacTab.roles;
|
if (location.startsWith(RouteConstants.roles)) return RbacTab.roles;
|
||||||
@ -69,7 +70,7 @@ class _UsersRoleManagementScreenState
|
|||||||
);
|
);
|
||||||
if (saved == true && mounted) {
|
if (saved == true && mounted) {
|
||||||
ref.invalidate(usersListProvider);
|
ref.invalidate(usersListProvider);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
userId == null ? 'User added successfully' : 'User updated successfully',
|
userId == null ? 'User added successfully' : 'User updated successfully',
|
||||||
@ -90,7 +91,7 @@ class _UsersRoleManagementScreenState
|
|||||||
if (saved == true && mounted) {
|
if (saved == true && mounted) {
|
||||||
ref.invalidate(rolesListProvider);
|
ref.invalidate(rolesListProvider);
|
||||||
ref.invalidate(usersListProvider);
|
ref.invalidate(usersListProvider);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
roleId == null ? 'Role created successfully' : 'Role updated successfully',
|
roleId == null ? 'Role created successfully' : 'Role updated successfully',
|
||||||
@ -106,7 +107,7 @@ class _UsersRoleManagementScreenState
|
|||||||
|
|
||||||
Future<void> _deleteRole(RoleCardModel role) async {
|
Future<void> _deleteRole(RoleCardModel role) async {
|
||||||
if (isProtectedRole(role)) {
|
if (isProtectedRole(role)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Super Admin role cannot be deleted')),
|
const SnackBar(content: Text('Super Admin role cannot be deleted')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -128,7 +129,7 @@ class _UsersRoleManagementScreenState
|
|||||||
ref.invalidate(usersListProvider);
|
ref.invalidate(usersListProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(success ? 'Role deleted successfully' : 'Failed to delete role'),
|
content: Text(success ? 'Role deleted successfully' : 'Failed to delete role'),
|
||||||
),
|
),
|
||||||
@ -594,7 +595,7 @@ class _UsersTabState extends ConsumerState<_UsersTab> {
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
result.failure == null
|
result.failure == null
|
||||||
@ -612,7 +613,7 @@ class _UsersTabState extends ConsumerState<_UsersTab> {
|
|||||||
if (file == null) {
|
if (file == null) {
|
||||||
final error = ref.read(usersListProvider).valueOrNull?.actionError;
|
final error = ref.read(usersListProvider).valueOrNull?.actionError;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(error)),
|
SnackBar(content: Text(error)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -625,7 +626,7 @@ class _UsersTabState extends ConsumerState<_UsersTab> {
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
saved ? 'Downloaded ${file.fileName}' : 'Export cancelled',
|
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);
|
final success = await ref.read(usersListProvider.notifier).deactivateUser(user.id);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(success ? 'User deactivated' : 'Failed to deactivate user'),
|
content: Text(success ? 'User deactivated' : 'Failed to deactivate user'),
|
||||||
),
|
),
|
||||||
@ -1232,7 +1233,7 @@ class _PermissionMatrixTabState extends ConsumerState<_PermissionMatrixTab> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _isSaving = false);
|
setState(() => _isSaving = false);
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Row(
|
content: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import '../../../../shared/widgets/error_view.dart';
|
|||||||
import '../../../../shared/widgets/app_side_panel.dart';
|
import '../../../../shared/widgets/app_side_panel.dart';
|
||||||
import '../../../master_data/presentation/widgets/master_quick_add.dart';
|
import '../../../master_data/presentation/widgets/master_quick_add.dart';
|
||||||
import '../providers/add_user_form_provider.dart';
|
import '../providers/add_user_form_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class AddUserPanel extends ConsumerStatefulWidget {
|
class AddUserPanel extends ConsumerStatefulWidget {
|
||||||
const AddUserPanel({super.key, this.userId});
|
const AddUserPanel({super.key, this.userId});
|
||||||
@ -109,7 +110,7 @@ class _AddUserPanelState extends ConsumerState<AddUserPanel> {
|
|||||||
.whereType<int>()
|
.whereType<int>()
|
||||||
.toList();
|
.toList();
|
||||||
if (roleIds.isEmpty) {
|
if (roleIds.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Please select at least one role')),
|
const SnackBar(content: Text('Please select at least one role')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -169,7 +170,7 @@ class _AddUserPanelState extends ConsumerState<AddUserPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final error = ref.read(addUserFormProvider(widget.userId)).valueOrNull?.errorMessage;
|
final error = ref.read(addUserFormProvider(widget.userId)).valueOrNull?.errorMessage;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
error ?? (widget.isEditing ? 'Failed to update user' : 'Failed to create user'),
|
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 '../../../../shared/widgets/app_side_panel.dart';
|
||||||
import '../providers/role_form_provider.dart';
|
import '../providers/role_form_provider.dart';
|
||||||
import 'rbac_widgets.dart';
|
import 'rbac_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class RoleFormPanel extends ConsumerStatefulWidget {
|
class RoleFormPanel extends ConsumerStatefulWidget {
|
||||||
const RoleFormPanel({super.key, this.roleId});
|
const RoleFormPanel({super.key, this.roleId});
|
||||||
@ -82,7 +83,7 @@ class _RoleFormPanelState extends ConsumerState<RoleFormPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final error = ref.read(roleFormProvider(widget.roleId)).valueOrNull?.errorMessage;
|
final error = ref.read(roleFormProvider(widget.roleId)).valueOrNull?.errorMessage;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
error ??
|
error ??
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import '../../../../shared/widgets/error_view.dart';
|
|||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
import '../../domain/entities/depreciation_report.dart';
|
import '../../domain/entities/depreciation_report.dart';
|
||||||
import '../providers/depreciation_report_provider.dart';
|
import '../providers/depreciation_report_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class DepreciationReportScreen extends ConsumerStatefulWidget {
|
class DepreciationReportScreen extends ConsumerStatefulWidget {
|
||||||
const DepreciationReportScreen({super.key});
|
const DepreciationReportScreen({super.key});
|
||||||
@ -52,8 +53,7 @@ class _DepreciationReportScreenState
|
|||||||
final error =
|
final error =
|
||||||
ref.read(depreciationReportProvider).valueOrNull?.actionError;
|
ref.read(depreciationReportProvider).valueOrNull?.actionError;
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
|
||||||
.showSnackBar(SnackBar(content: Text(error)));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@ class _DepreciationReportScreenState
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
saved ? 'Downloaded ${file.fileName}' : 'Export cancelled',
|
saved ? 'Downloaded ${file.fileName}' : 'Export cancelled',
|
||||||
@ -119,8 +119,7 @@ class _DepreciationReportScreenState
|
|||||||
ref.listen(depreciationReportProvider, (prev, next) {
|
ref.listen(depreciationReportProvider, (prev, next) {
|
||||||
final error = next.valueOrNull?.actionError;
|
final error = next.valueOrNull?.actionError;
|
||||||
if (error != null && error != prev?.valueOrNull?.actionError) {
|
if (error != null && error != prev?.valueOrNull?.actionError) {
|
||||||
ScaffoldMessenger.of(context)
|
showAppToastFromSnackBar(context, SnackBar(content: Text(error)));
|
||||||
.showSnackBar(SnackBar(content: Text(error)));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import '../../../../shared/widgets/app_loading_view.dart';
|
|||||||
import '../../../../shared/widgets/error_view.dart';
|
import '../../../../shared/widgets/error_view.dart';
|
||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
import '../providers/roles_provider.dart';
|
import '../providers/roles_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class PermissionMatrixScreen extends ConsumerStatefulWidget {
|
class PermissionMatrixScreen extends ConsumerStatefulWidget {
|
||||||
const PermissionMatrixScreen({super.key, required this.roleId});
|
const PermissionMatrixScreen({super.key, required this.roleId});
|
||||||
@ -30,7 +31,7 @@ class _PermissionMatrixScreenState extends ConsumerState<PermissionMatrixScreen>
|
|||||||
await ref.read(permissionMatrixProvider(widget.roleId).notifier).save();
|
await ref.read(permissionMatrixProvider(widget.roleId).notifier).save();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _isSaving = false);
|
setState(() => _isSaving = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(success ? 'Permissions saved' : 'Failed to save permissions'),
|
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 '../../../../shared/widgets/app_button.dart';
|
||||||
import '../providers/settings_provider.dart';
|
import '../providers/settings_provider.dart';
|
||||||
import '../widgets/settings_widgets.dart';
|
import '../widgets/settings_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class AppearanceSettingsScreen extends ConsumerWidget {
|
class AppearanceSettingsScreen extends ConsumerWidget {
|
||||||
const AppearanceSettingsScreen({super.key});
|
const AppearanceSettingsScreen({super.key});
|
||||||
@ -182,7 +183,7 @@ class AppearanceSettingsScreen extends ConsumerWidget {
|
|||||||
AppButton(
|
AppButton(
|
||||||
label: 'Settings Auto-Saved',
|
label: 'Settings Auto-Saved',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Appearance settings are saved automatically')),
|
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 '../../domain/entities/app_settings.dart';
|
||||||
import '../providers/settings_provider.dart';
|
import '../providers/settings_provider.dart';
|
||||||
import '../widgets/settings_widgets.dart';
|
import '../widgets/settings_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class AssetSettingsScreen extends ConsumerStatefulWidget {
|
class AssetSettingsScreen extends ConsumerStatefulWidget {
|
||||||
const AssetSettingsScreen({super.key});
|
const AssetSettingsScreen({super.key});
|
||||||
@ -73,7 +74,7 @@ class _AssetSettingsScreenState extends ConsumerState<AssetSettingsScreen> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Asset settings saved')),
|
const SnackBar(content: Text('Asset settings saved')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import '../../../../shared/widgets/sidebar_logo.dart';
|
|||||||
import '../../domain/entities/app_settings.dart';
|
import '../../domain/entities/app_settings.dart';
|
||||||
import '../providers/settings_provider.dart';
|
import '../providers/settings_provider.dart';
|
||||||
import '../widgets/settings_widgets.dart';
|
import '../widgets/settings_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class CompanyProfileSettingsScreen extends ConsumerStatefulWidget {
|
class CompanyProfileSettingsScreen extends ConsumerStatefulWidget {
|
||||||
const CompanyProfileSettingsScreen({super.key});
|
const CompanyProfileSettingsScreen({super.key});
|
||||||
@ -82,7 +83,7 @@ class _CompanyProfileSettingsScreenState
|
|||||||
_applyProfile(ref.read(appSettingsProvider).companyProfile);
|
_applyProfile(ref.read(appSettingsProvider).companyProfile);
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
if (failure != null) {
|
if (failure != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(failure.message),
|
content: Text(failure.message),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
@ -158,7 +159,7 @@ class _CompanyProfileSettingsScreenState
|
|||||||
setState(() => _saving = false);
|
setState(() => _saving = false);
|
||||||
|
|
||||||
if (failure != null) {
|
if (failure != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(validationErrorMessage(failure)),
|
content: Text(validationErrorMessage(failure)),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
@ -175,7 +176,7 @@ class _CompanyProfileSettingsScreenState
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Company profile saved')),
|
const SnackBar(content: Text('Company profile saved')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -207,14 +208,14 @@ class _CompanyProfileSettingsScreenState
|
|||||||
uploadedUrl != null &&
|
uploadedUrl != null &&
|
||||||
uploadedUrl.isNotEmpty) {
|
uploadedUrl.isNotEmpty) {
|
||||||
setState(() => _logoUrlController.text = uploadedUrl);
|
setState(() => _logoUrlController.text = uploadedUrl);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Logo uploaded')),
|
const SnackBar(content: Text('Logo uploaded')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploadResult.failure != null) {
|
if (uploadResult.failure != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(uploadResult.failure!.message),
|
content: Text(uploadResult.failure!.message),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
@ -268,14 +269,14 @@ class _CompanyProfileSettingsScreenState
|
|||||||
updateFavicon(previewDataUri);
|
updateFavicon(previewDataUri);
|
||||||
|
|
||||||
setState(() => _faviconUrlController.text = uploadedUrl);
|
setState(() => _faviconUrlController.text = uploadedUrl);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Favicon uploaded')),
|
const SnackBar(content: Text('Favicon uploaded')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploadResult.failure != null) {
|
if (uploadResult.failure != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(uploadResult.failure!.message),
|
content: Text(uploadResult.failure!.message),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import '../../../../shared/widgets/app_text_field.dart';
|
|||||||
import '../../domain/entities/app_settings.dart';
|
import '../../domain/entities/app_settings.dart';
|
||||||
import '../providers/settings_provider.dart';
|
import '../providers/settings_provider.dart';
|
||||||
import '../widgets/settings_widgets.dart';
|
import '../widgets/settings_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class EmailConfigurationScreen extends ConsumerStatefulWidget {
|
class EmailConfigurationScreen extends ConsumerStatefulWidget {
|
||||||
const EmailConfigurationScreen({super.key});
|
const EmailConfigurationScreen({super.key});
|
||||||
@ -51,7 +52,7 @@ class _EmailConfigurationScreenState
|
|||||||
_applyEmail(ref.read(appSettingsProvider).email);
|
_applyEmail(ref.read(appSettingsProvider).email);
|
||||||
setState(() => _loading = false);
|
setState(() => _loading = false);
|
||||||
if (failure != null) {
|
if (failure != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(failure.message),
|
content: Text(failure.message),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
@ -87,7 +88,7 @@ class _EmailConfigurationScreenState
|
|||||||
|
|
||||||
final port = int.tryParse(_portController.text.trim());
|
final port = int.tryParse(_portController.text.trim());
|
||||||
if (port == null || port <= 0) {
|
if (port == null || port <= 0) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Enter a valid SMTP port')),
|
const SnackBar(content: Text('Enter a valid SMTP port')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -109,7 +110,7 @@ class _EmailConfigurationScreenState
|
|||||||
setState(() => _saving = false);
|
setState(() => _saving = false);
|
||||||
|
|
||||||
if (failure != null) {
|
if (failure != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(validationErrorMessage(failure)),
|
content: Text(validationErrorMessage(failure)),
|
||||||
backgroundColor: Theme.of(context).colorScheme.error,
|
backgroundColor: Theme.of(context).colorScheme.error,
|
||||||
@ -118,7 +119,7 @@ class _EmailConfigurationScreenState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Email configuration saved')),
|
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 '../../domain/entities/app_settings.dart';
|
||||||
import '../providers/settings_provider.dart';
|
import '../providers/settings_provider.dart';
|
||||||
import '../widgets/settings_widgets.dart';
|
import '../widgets/settings_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class GeneralSettingsScreen extends ConsumerStatefulWidget {
|
class GeneralSettingsScreen extends ConsumerStatefulWidget {
|
||||||
const GeneralSettingsScreen({super.key});
|
const GeneralSettingsScreen({super.key});
|
||||||
@ -64,7 +65,7 @@ class _GeneralSettingsScreenState extends ConsumerState<GeneralSettingsScreen> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('General settings saved')),
|
const SnackBar(content: Text('General settings saved')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import '../../../../shared/widgets/app_button.dart';
|
|||||||
import '../../domain/entities/app_settings.dart';
|
import '../../domain/entities/app_settings.dart';
|
||||||
import '../providers/settings_provider.dart';
|
import '../providers/settings_provider.dart';
|
||||||
import '../widgets/settings_widgets.dart';
|
import '../widgets/settings_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class NotificationSettingsScreen extends ConsumerWidget {
|
class NotificationSettingsScreen extends ConsumerWidget {
|
||||||
const NotificationSettingsScreen({super.key});
|
const NotificationSettingsScreen({super.key});
|
||||||
@ -16,7 +17,7 @@ class NotificationSettingsScreen extends ConsumerWidget {
|
|||||||
Future<void> update(NotificationSettingsConfig updated) async {
|
Future<void> update(NotificationSettingsConfig updated) async {
|
||||||
await ref.read(appSettingsProvider.notifier).updateNotifications(updated);
|
await ref.read(appSettingsProvider.notifier).updateNotifications(updated);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Notification settings saved')),
|
const SnackBar(content: Text('Notification settings saved')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -86,7 +87,7 @@ class NotificationSettingsScreen extends ConsumerWidget {
|
|||||||
AppButton(
|
AppButton(
|
||||||
label: 'Settings Auto-Saved',
|
label: 'Settings Auto-Saved',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Notification settings are saved automatically'),
|
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 '../../domain/entities/app_settings.dart';
|
||||||
import '../providers/settings_provider.dart';
|
import '../providers/settings_provider.dart';
|
||||||
import '../widgets/settings_widgets.dart';
|
import '../widgets/settings_widgets.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class SecuritySettingsScreen extends ConsumerStatefulWidget {
|
class SecuritySettingsScreen extends ConsumerStatefulWidget {
|
||||||
const SecuritySettingsScreen({super.key});
|
const SecuritySettingsScreen({super.key});
|
||||||
@ -86,7 +87,7 @@ class _SecuritySettingsScreenState extends ConsumerState<SecuritySettingsScreen>
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Security settings saved')),
|
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/widgets/page_header.dart';
|
||||||
import '../../../../shared/models/user_management_models.dart';
|
import '../../../../shared/models/user_management_models.dart';
|
||||||
import '../providers/users_provider.dart';
|
import '../providers/users_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class UserDetailScreen extends ConsumerWidget {
|
class UserDetailScreen extends ConsumerWidget {
|
||||||
const UserDetailScreen({super.key, required this.userId});
|
const UserDetailScreen({super.key, required this.userId});
|
||||||
@ -99,7 +100,7 @@ class UserDetailScreen extends ConsumerWidget {
|
|||||||
final success = await ref.read(userDetailProvider(userId).notifier).deactivate();
|
final success = await ref.read(userDetailProvider(userId).notifier).deactivate();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(success ? 'User deactivated' : 'Failed to deactivate')),
|
SnackBar(content: Text(success ? 'User deactivated' : 'Failed to deactivate')),
|
||||||
);
|
);
|
||||||
if (success) context.pop();
|
if (success) context.pop();
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import '../../../../shared/widgets/app_text_field.dart';
|
|||||||
import '../../../../shared/widgets/error_view.dart';
|
import '../../../../shared/widgets/error_view.dart';
|
||||||
import '../../../master_data/presentation/widgets/master_quick_add.dart';
|
import '../../../master_data/presentation/widgets/master_quick_add.dart';
|
||||||
import '../providers/users_provider.dart';
|
import '../providers/users_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class UserFormScreen extends ConsumerStatefulWidget {
|
class UserFormScreen extends ConsumerStatefulWidget {
|
||||||
const UserFormScreen({super.key, this.userId});
|
const UserFormScreen({super.key, this.userId});
|
||||||
@ -83,7 +84,7 @@ class _UserFormScreenState extends ConsumerState<UserFormScreen> {
|
|||||||
Future<void> _submit() async {
|
Future<void> _submit() async {
|
||||||
if (!_formKey.currentState!.validate()) return;
|
if (!_formKey.currentState!.validate()) return;
|
||||||
if (_selectedRoleId == null) {
|
if (_selectedRoleId == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Please select a role')),
|
const SnackBar(content: Text('Please select a role')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -127,13 +128,13 @@ class _UserFormScreenState extends ConsumerState<UserFormScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(isEditing ? 'User updated' : 'User created')),
|
SnackBar(content: Text(isEditing ? 'User updated' : 'User created')),
|
||||||
);
|
);
|
||||||
context.pop();
|
context.pop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(e.toString())),
|
SnackBar(content: Text(e.toString())),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import '../../../../shared/widgets/app_table_action_icon.dart';
|
|||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
import '../widgets/user_rich_data_table.dart';
|
import '../widgets/user_rich_data_table.dart';
|
||||||
import '../providers/users_provider.dart';
|
import '../providers/users_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class UserListScreen extends ConsumerStatefulWidget {
|
class UserListScreen extends ConsumerStatefulWidget {
|
||||||
const UserListScreen({super.key});
|
const UserListScreen({super.key});
|
||||||
@ -151,7 +152,7 @@ class _UserListScreenState extends ConsumerState<UserListScreen> {
|
|||||||
Future<void> _toggleStatus(ManagedUserModel user) async {
|
Future<void> _toggleStatus(ManagedUserModel user) async {
|
||||||
final success = await ref.read(usersListProvider.notifier).toggleUserStatus(user);
|
final success = await ref.read(usersListProvider.notifier).toggleUserStatus(user);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(success ? 'User status updated' : 'Failed to update status'),
|
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);
|
final success = await ref.read(usersListProvider.notifier).deactivateUser(user.id);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(success ? 'User deactivated' : 'Failed to deactivate user')),
|
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 '../../../../shared/widgets/app_text_field.dart';
|
||||||
import '../../../auth/data/repositories/auth_repository_impl.dart';
|
import '../../../auth/data/repositories/auth_repository_impl.dart';
|
||||||
import '../providers/users_provider.dart';
|
import '../providers/users_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class UserProfileScreen extends ConsumerStatefulWidget {
|
class UserProfileScreen extends ConsumerStatefulWidget {
|
||||||
const UserProfileScreen({super.key});
|
const UserProfileScreen({super.key});
|
||||||
@ -62,7 +63,7 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
|
|||||||
);
|
);
|
||||||
if (result == null || result.files.isEmpty) return;
|
if (result == null || result.files.isEmpty) return;
|
||||||
setState(() => _avatarUrl = result.files.first.path);
|
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.')),
|
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);
|
setState(() => _isUpdatingProfile = false);
|
||||||
|
|
||||||
if (result.failure != null) {
|
if (result.failure != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(result.failure.toString())),
|
SnackBar(content: Text(result.failure.toString())),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ref.read(authStateProvider.notifier).checkAuth();
|
await ref.read(authStateProvider.notifier).checkAuth();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Profile updated')),
|
const SnackBar(content: Text('Profile updated')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _changePassword() async {
|
Future<void> _changePassword() async {
|
||||||
if (_newPasswordController.text != _confirmPasswordController.text) {
|
if (_newPasswordController.text != _confirmPasswordController.text) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Passwords do not match')),
|
const SnackBar(content: Text('Passwords do not match')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -106,7 +107,7 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
|
|||||||
|
|
||||||
final passwordError = Validators.password(_newPasswordController.text);
|
final passwordError = Validators.password(_newPasswordController.text);
|
||||||
if (passwordError != null) {
|
if (passwordError != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(passwordError)));
|
showAppToastFromSnackBar(context, SnackBar(content: Text(passwordError)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +125,7 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
|
|||||||
setState(() => _isChangingPassword = false);
|
setState(() => _isChangingPassword = false);
|
||||||
|
|
||||||
if (result.failure != null) {
|
if (result.failure != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(result.failure.toString())),
|
SnackBar(content: Text(result.failure.toString())),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -133,7 +134,7 @@ class _UserProfileScreenState extends ConsumerState<UserProfileScreen> {
|
|||||||
_currentPasswordController.clear();
|
_currentPasswordController.clear();
|
||||||
_newPasswordController.clear();
|
_newPasswordController.clear();
|
||||||
_confirmPasswordController.clear();
|
_confirmPasswordController.clear();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Password changed successfully')),
|
const SnackBar(content: Text('Password changed successfully')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import '../../../../shared/widgets/page_header.dart';
|
|||||||
import '../providers/vendors_provider.dart';
|
import '../providers/vendors_provider.dart';
|
||||||
import '../widgets/vendor_form_panel.dart';
|
import '../widgets/vendor_form_panel.dart';
|
||||||
import '../widgets/vendor_sub_resource_panels.dart';
|
import '../widgets/vendor_sub_resource_panels.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class VendorDetailScreen extends ConsumerStatefulWidget {
|
class VendorDetailScreen extends ConsumerStatefulWidget {
|
||||||
const VendorDetailScreen({super.key, required this.vendorId});
|
const VendorDetailScreen({super.key, required this.vendorId});
|
||||||
@ -171,13 +172,13 @@ class _VendorDetailScreenState extends ConsumerState<VendorDetailScreen>
|
|||||||
.read(vendorDetailProvider(widget.vendorId).notifier)
|
.read(vendorDetailProvider(widget.vendorId).notifier)
|
||||||
.updateStatus(selected!);
|
.updateStatus(selected!);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Vendor status updated')),
|
const SnackBar(content: Text('Vendor status updated')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
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);
|
if (mounted) context.go(RouteConstants.vendors);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
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,
|
vendorId: vendorId,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Address saved')),
|
const SnackBar(content: Text('Address saved')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -450,7 +451,7 @@ class _AddressesTab extends ConsumerWidget {
|
|||||||
await ref.read(vendorDetailProvider(vendorId).notifier).deleteAddress(addressId);
|
await ref.read(vendorDetailProvider(vendorId).notifier).deleteAddress(addressId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
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,
|
vendorId: vendorId,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Contact saved')),
|
const SnackBar(content: Text('Contact saved')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -550,7 +551,7 @@ class _ContactsTab extends ConsumerWidget {
|
|||||||
await ref.read(vendorDetailProvider(vendorId).notifier).deleteContact(contactId);
|
await ref.read(vendorDetailProvider(vendorId).notifier).deleteContact(contactId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
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,
|
vendorId: vendorId,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Bank detail saved')),
|
const SnackBar(content: Text('Bank detail saved')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -652,7 +653,7 @@ class _BankDetailsTab extends ConsumerWidget {
|
|||||||
.deleteBankDetail(bankDetailId);
|
.deleteBankDetail(bankDetailId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
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 '../../../../shared/widgets/page_header.dart';
|
||||||
import '../providers/vendors_provider.dart';
|
import '../providers/vendors_provider.dart';
|
||||||
import '../widgets/vendor_form_panel.dart';
|
import '../widgets/vendor_form_panel.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
class VendorListScreen extends ConsumerStatefulWidget {
|
class VendorListScreen extends ConsumerStatefulWidget {
|
||||||
const VendorListScreen({super.key});
|
const VendorListScreen({super.key});
|
||||||
@ -52,10 +53,10 @@ class _VendorListScreenState extends ConsumerState<VendorListScreen> {
|
|||||||
final error = next.valueOrNull?.actionError;
|
final error = next.valueOrNull?.actionError;
|
||||||
final success = next.valueOrNull?.actionSuccess;
|
final success = next.valueOrNull?.actionSuccess;
|
||||||
if (error != null && error != prev?.valueOrNull?.actionError) {
|
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) {
|
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 '../../../master_data/presentation/widgets/master_quick_add.dart';
|
||||||
import '../../data/repositories/vendor_repository_impl.dart';
|
import '../../data/repositories/vendor_repository_impl.dart';
|
||||||
import '../providers/vendors_provider.dart';
|
import '../providers/vendors_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
Future<void> openVendorFormPanel(
|
Future<void> openVendorFormPanel(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
@ -29,7 +30,7 @@ Future<void> openVendorFormPanel(
|
|||||||
width: 560,
|
width: 560,
|
||||||
);
|
);
|
||||||
if (saved == true && context.mounted) {
|
if (saved == true && context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
vendorId == null
|
vendorId == null
|
||||||
@ -136,7 +137,7 @@ class _VendorFormPanelState extends ConsumerState<VendorFormPanel> {
|
|||||||
Future<void> _submit() async {
|
Future<void> _submit() async {
|
||||||
if (!_formKey.currentState!.validate()) return;
|
if (!_formKey.currentState!.validate()) return;
|
||||||
if (_vendorType == null) {
|
if (_vendorType == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Please select vendor type')),
|
const SnackBar(content: Text('Please select vendor type')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -154,7 +155,7 @@ class _VendorFormPanelState extends ConsumerState<VendorFormPanel> {
|
|||||||
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
|
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(e.toString())),
|
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_side_panel.dart';
|
||||||
import '../../../../shared/widgets/app_text_field.dart';
|
import '../../../../shared/widgets/app_text_field.dart';
|
||||||
import '../providers/vendors_provider.dart';
|
import '../providers/vendors_provider.dart';
|
||||||
|
import '../../../../shared/widgets/app_toast.dart';
|
||||||
|
|
||||||
Future<bool?> openVendorAddressPanel(
|
Future<bool?> openVendorAddressPanel(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@ -102,7 +103,7 @@ class _VendorAddressPanelState extends ConsumerState<VendorAddressPanel> {
|
|||||||
Future<void> _save() async {
|
Future<void> _save() async {
|
||||||
if (!_formKey.currentState!.validate()) return;
|
if (!_formKey.currentState!.validate()) return;
|
||||||
if (_addressType == null) {
|
if (_addressType == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Please select address type')),
|
const SnackBar(content: Text('Please select address type')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -137,7 +138,7 @@ class _VendorAddressPanelState extends ConsumerState<VendorAddressPanel> {
|
|||||||
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
|
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isSubmitting = false);
|
if (mounted) setState(() => _isSubmitting = false);
|
||||||
@ -279,7 +280,7 @@ class _VendorContactPanelState extends ConsumerState<VendorContactPanel> {
|
|||||||
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
|
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isSubmitting = false);
|
if (mounted) setState(() => _isSubmitting = false);
|
||||||
@ -427,7 +428,7 @@ class _VendorBankDetailPanelState extends ConsumerState<VendorBankDetailPanel> {
|
|||||||
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
|
if (mounted) Navigator.of(context, rootNavigator: true).pop(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
|
showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString())));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _isSubmitting = false);
|
if (mounted) setState(() => _isSubmitting = false);
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
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).
|
/// 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({
|
Future<bool> downloadFile({
|
||||||
required List<int> bytes,
|
required List<int> bytes,
|
||||||
required String fileName,
|
required String fileName,
|
||||||
@ -11,5 +17,6 @@ Future<bool> downloadFile({
|
|||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
bytes: Uint8List.fromList(bytes),
|
bytes: Uint8List.fromList(bytes),
|
||||||
);
|
);
|
||||||
|
if (kIsWeb) return true;
|
||||||
return path != null;
|
return path != null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,22 +3,13 @@ import 'package:flutter/material.dart';
|
|||||||
import '../../core/errors/failure.dart';
|
import '../../core/errors/failure.dart';
|
||||||
import '../../core/network/api_handler.dart';
|
import '../../core/network/api_handler.dart';
|
||||||
import 'app_side_panel.dart';
|
import 'app_side_panel.dart';
|
||||||
|
import 'app_toast.dart';
|
||||||
|
|
||||||
void showAccessDeniedSnackBar(BuildContext context, {String? message}) {
|
void showAccessDeniedSnackBar(BuildContext context, {String? message}) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToast(
|
||||||
SnackBar(
|
context,
|
||||||
content: Row(
|
message ?? 'Access denied',
|
||||||
children: [
|
type: AppToastType.error,
|
||||||
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,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,19 +29,15 @@ void showApiFailureSnackBar(BuildContext context, Failure failure) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isConflictFailure(failure)) {
|
if (isConflictFailure(failure)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToast(context, failure.message, type: AppToastType.warning);
|
||||||
SnackBar(content: Text(failure.message)),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToast(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(
|
failure is ValidationFailure
|
||||||
failure is ValidationFailure
|
? validationErrorMessage(failure)
|
||||||
? validationErrorMessage(failure)
|
: failure.message,
|
||||||
: failure.message,
|
type: AppToastType.error,
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../core/utils/responsive_utils.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).
|
/// Holds a full-width Quick Add form below a form row (so it stays clickable).
|
||||||
class QuickAddInlineController extends ChangeNotifier {
|
class QuickAddInlineController extends ChangeNotifier {
|
||||||
@ -151,13 +152,13 @@ void showSidePanelSnackBar(
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
String message, {
|
String message, {
|
||||||
Duration duration = const Duration(seconds: 4),
|
Duration duration = const Duration(seconds: 4),
|
||||||
|
AppToastType? type,
|
||||||
}) {
|
}) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToast(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(message),
|
message,
|
||||||
behavior: SnackBarBehavior.floating,
|
type: type ?? inferToastType(message),
|
||||||
duration: duration,
|
duration: duration,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import '../../core/constants/enums.dart';
|
|||||||
import '../../core/constants/route_constants.dart';
|
import '../../core/constants/route_constants.dart';
|
||||||
import '../../core/theme/app_colors.dart';
|
import '../../core/theme/app_colors.dart';
|
||||||
import '../../core/theme/theme_provider.dart';
|
import '../../core/theme/theme_provider.dart';
|
||||||
import '../../core/theme/theme_provider.dart';
|
|
||||||
import '../../core/utils/favicon_store.dart';
|
import '../../core/utils/favicon_store.dart';
|
||||||
import '../../modules/settings/presentation/providers/settings_provider.dart';
|
import '../../modules/settings/presentation/providers/settings_provider.dart';
|
||||||
import '../models/user_model.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 '../models/entity_attachment_model.dart';
|
||||||
import '../utils/file_download_helper.dart';
|
import '../utils/file_download_helper.dart';
|
||||||
import 'app_confirmation_dialog.dart';
|
import 'app_confirmation_dialog.dart';
|
||||||
|
import 'app_toast.dart';
|
||||||
|
|
||||||
const kEntityAttachmentExtensions = ['pdf', 'jpg', 'jpeg', 'png', 'webp'];
|
const kEntityAttachmentExtensions = ['pdf', 'jpg', 'jpeg', 'png', 'webp'];
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
|
|||||||
final bytes = file.bytes;
|
final bytes = file.bytes;
|
||||||
if (bytes == null || bytes.isEmpty) {
|
if (bytes == null || bytes.isEmpty) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Could not read the selected file')),
|
const SnackBar(content: Text('Could not read the selected file')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -89,7 +90,7 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
|
|||||||
final ext = (file.extension ?? '').toLowerCase();
|
final ext = (file.extension ?? '').toLowerCase();
|
||||||
if (!kEntityAttachmentExtensions.contains(ext)) {
|
if (!kEntityAttachmentExtensions.contains(ext)) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Only PDF, JPEG, PNG, and WebP files are allowed'),
|
content: Text('Only PDF, JPEG, PNG, and WebP files are allowed'),
|
||||||
),
|
),
|
||||||
@ -101,14 +102,14 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
|
|||||||
try {
|
try {
|
||||||
await widget.onUpload(bytes: bytes, filename: file.name);
|
await widget.onUpload(bytes: bytes, filename: file.name);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text('${file.name} uploaded')),
|
SnackBar(content: Text('${file.name} uploaded')),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final message =
|
final message =
|
||||||
e is Failure ? validationErrorMessage(e) : e.toString();
|
e is Failure ? validationErrorMessage(e) : e.toString();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(message)),
|
SnackBar(content: Text(message)),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@ -127,7 +128,7 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(e.toString())),
|
SnackBar(content: Text(e.toString())),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@ -152,14 +153,14 @@ class _EntityAttachmentsCardState extends State<EntityAttachmentsCard> {
|
|||||||
try {
|
try {
|
||||||
await widget.onDelete(attachment.id);
|
await widget.onDelete(attachment.id);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
const SnackBar(content: Text('Attachment deleted')),
|
const SnackBar(content: Text('Attachment deleted')),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final message =
|
final message =
|
||||||
e is Failure ? validationErrorMessage(e) : e.toString();
|
e is Failure ? validationErrorMessage(e) : e.toString();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
showAppToastFromSnackBar(context,
|
||||||
SnackBar(content: Text(message)),
|
SnackBar(content: Text(message)),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user