From 167bd99e0a63172e4872338fea2cfc5978cce107 Mon Sep 17 00:00:00 2001 From: SurendarSuri30 Date: Fri, 24 Jul 2026 14:01:50 +0530 Subject: [PATCH] bug fix --- README.md | 16 +- lib/core/config/environment.dart | 14 -- lib/core/theme/app_theme.dart | 4 +- lib/core/theme/app_typography.dart | 206 ++++++++++++++---- .../screens/asset_alerts_screen.dart | 2 - .../screens/asset_form_screen.dart | 64 +++++- .../widgets/asset_maintenance_panel.dart | 3 +- .../widgets/asset_side_panels.dart | 6 +- .../screens/change_password_screen.dart | 61 ++---- .../screens/forgot_password_screen.dart | 14 +- .../presentation/screens/login_screen.dart | 134 ++++-------- .../auth/presentation/widgets/bcpl_logo.dart | 4 +- .../widgets/login_hero_illustration.dart | 9 +- .../widgets/login_hero_panel.dart | 48 ++-- .../screens/grn_detail_screen.dart | 11 +- .../presentation/screens/grn_form_screen.dart | 16 +- .../widgets/grn_attachments_card.dart | 22 +- .../presentation/widgets/grn_status_chip.dart | 15 +- .../screens/masters_hub_screen.dart | 1 - .../widgets/master_form_panel.dart | 10 +- .../widgets/master_quick_add.dart | 5 + .../screens/purchase_order_detail_screen.dart | 26 ++- .../screens/purchase_order_form_screen.dart | 5 +- .../presentation/widgets/po_status_chip.dart | 18 +- .../presentation/widgets/rbac_widgets.dart | 6 +- .../providers/users_provider.dart | 8 +- .../screens/user_form_screen.dart | 6 +- .../screens/user_profile_screen.dart | 6 +- .../screens/vendor_detail_screen.dart | 31 ++- .../widgets/vendor_form_panel.dart | 6 +- .../widgets/vendor_sub_resource_panels.dart | 16 +- lib/shared/widgets/api_feedback.dart | 34 ++- lib/shared/widgets/app_status_chip.dart | 32 ++- lib/shared/widgets/app_top_nav.dart | 5 +- .../widgets/entity_attachments_card.dart | 22 +- .../widgets/master_inline_quick_add_form.dart | 3 +- lib/shared/widgets/user_avatar.dart | 6 +- web/.htaccess | 6 +- web/index.html | 2 +- 39 files changed, 559 insertions(+), 344 deletions(-) diff --git a/README.md b/README.md index aa37da6..57f0a08 100644 --- a/README.md +++ b/README.md @@ -46,25 +46,25 @@ dart run build_runner build --delete-conflicting-outputs Each environment has its own entry point in `lib/config/` that sets `Environment.flavor`. API URL and `.env` file are picked automatically (see `lib/core/config/environment.dart`). -| Flavor | Entry point | API URL (auto) | Web URL | Web base-href | -|--------|-------------|----------------|---------|---------------| -| **dev** | `lib/config/main_dev.dart` | `https://demo.venbait.in/api/v1` | `https://bharatconsumerproducts.com/erp/login` | `/erp/` | -| **uat** | `lib/config/main_uat.dart` | `https://uat-api.bharaterp.com/api/v1` | — | `/` | -| **prod** | `lib/config/main_prod.dart` | `https://api.bharaterp.com/api/v1` | — | `/app/` | +| Flavor | Entry point | API URL (auto) | Web base-href | +|--------|-------------|----------------|---------------| +| **dev** | `lib/config/main_dev.dart` | `https://demo.venbait.in/api/v1` | `/` | +| **uat** | `lib/config/main_uat.dart` | `https://uat-api.bharaterp.com/api/v1` | `/` | +| **prod** | `lib/config/main_prod.dart` | `https://api.bharaterp.com/api/v1` | `/` | -Dev web uses **path URLs** (no `index.html#`). Build with `--base-href /erp/` and deploy `web/.htaccess` for Apache SPA fallback. +Dev web uses **path URLs** (no `index.html#`). Build with `--base-href /` and deploy `web/.htaccess` for Apache/LiteSpeed SPA fallback. ### Web builds ```bash # Dev -flutter build web -t lib/config/main_dev.dart --release --base-href /erp/ +flutter build web -t lib/config/main_dev.dart --release --base-href / # UAT flutter build web -t lib/config/main_uat.dart --release --base-href / # Prod -flutter build web -t lib/config/main_prod.dart --release --base-href /app/ +flutter build web -t lib/config/main_prod.dart --release --base-href / ``` ### Android APK / AAB diff --git a/lib/core/config/environment.dart b/lib/core/config/environment.dart index 8eb71a4..8047bd5 100644 --- a/lib/core/config/environment.dart +++ b/lib/core/config/environment.dart @@ -17,20 +17,6 @@ class Environment { Flavor.prod => 'production', }; - /// Web deploy path (`--base-href` should match this value). - static String get baseHref => switch (flavor) { - Flavor.dev => '/erp/', - Flavor.uat => '/', - Flavor.prod => '/app/', - }; - - /// Public web app URL (path-based routing — no `index.html#`). - static String get webAppUrl => switch (flavor) { - Flavor.dev => 'https://bharatconsumerproducts.com/erp', - Flavor.uat => 'https://uat.bharaterp.com', - Flavor.prod => 'https://app.bharaterp.com', - }; - static String get envFileName => switch (flavor) { Flavor.dev => '.env.development', Flavor.uat => '.env.uat', diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index 1c6bb02..6872729 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -201,10 +201,10 @@ class AppTheme { borderSide: BorderSide(color: colorScheme.primary, width: 2), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - labelStyle: textTheme.bodyMedium, + labelStyle: textTheme.labelLarge, hintStyle: textTheme.bodyMedium?.copyWith(color: colorScheme.onSurfaceVariant), helperStyle: textTheme.bodySmall, - errorStyle: textTheme.bodySmall?.copyWith(color: colorScheme.error), + errorStyle: textTheme.labelSmall?.copyWith(color: colorScheme.error), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( diff --git a/lib/core/theme/app_typography.dart b/lib/core/theme/app_typography.dart index 53811f5..7dd64ec 100644 --- a/lib/core/theme/app_typography.dart +++ b/lib/core/theme/app_typography.dart @@ -1,57 +1,189 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -/// ERP typography — Figtree (Google Font). -/// Weights: Regular 400, Medium 500, SemiBold 600. +/// ERP typography — Figtree design tokens. +/// +/// | Token | Sizes | Weights | +/// |--------------|-------|----------------------------------| +/// | Heading 1–6 | 56→20 | Medium, SemiBold, Bold | +/// | Body 1–4 | 18→12 | Regular, Medium | +/// | Label 1–3 | 16→12 | Regular, Medium, SemiBold, Bold | +/// | Caption 1–2 | 10→9 | Medium, SemiBold | class AppTypography { AppTypography._(); static const FontWeight regular = FontWeight.w400; static const FontWeight medium = FontWeight.w500; static const FontWeight semiBold = FontWeight.w600; + static const FontWeight bold = FontWeight.w700; static String get fontFamily => GoogleFonts.figtree().fontFamily ?? 'Figtree'; - static TextTheme textTheme(ColorScheme colorScheme) { - TextStyle token({ - required double size, - FontWeight weight = regular, - Color? color, - double? height, - }) { - return GoogleFonts.figtree( - fontSize: size, - fontWeight: weight, - letterSpacing: 0, - height: height, - color: color ?? colorScheme.onSurface, - ); - } + // ─── Design tokens ───────────────────────────────────────────────────────── + static TextStyle heading1({ + FontWeight weight = semiBold, + Color? color, + double? height, + }) => + _token(56, weight, color, height); + + static TextStyle heading2({ + FontWeight weight = semiBold, + Color? color, + double? height, + }) => + _token(48, weight, color, height); + + static TextStyle heading3({ + FontWeight weight = semiBold, + Color? color, + double? height, + }) => + _token(40, weight, color, height); + + static TextStyle heading4({ + FontWeight weight = semiBold, + Color? color, + double? height, + }) => + _token(32, weight, color, height); + + static TextStyle heading5({ + FontWeight weight = semiBold, + Color? color, + double? height, + }) => + _token(24, weight, color, height); + + static TextStyle heading6({ + FontWeight weight = semiBold, + Color? color, + double? height, + }) => + _token(20, weight, color, height); + + static TextStyle body1({ + FontWeight weight = regular, + Color? color, + double? height, + }) => + _token(18, weight, color, height); + + static TextStyle body2({ + FontWeight weight = regular, + Color? color, + double? height, + }) => + _token(16, weight, color, height); + + static TextStyle body3({ + FontWeight weight = regular, + Color? color, + double? height, + }) => + _token(14, weight, color, height); + + static TextStyle body4({ + FontWeight weight = regular, + Color? color, + double? height, + }) => + _token(12, weight, color, height); + + static TextStyle label1({ + FontWeight weight = medium, + Color? color, + double? height, + }) => + _token(16, weight, color, height); + + static TextStyle label2({ + FontWeight weight = medium, + Color? color, + double? height, + }) => + _token(14, weight, color, height); + + static TextStyle label3({ + FontWeight weight = medium, + Color? color, + double? height, + }) => + _token(12, weight, color, height); + + static TextStyle caption1({ + FontWeight weight = medium, + Color? color, + double? height, + }) => + _token(10, weight, color, height); + + static TextStyle caption2({ + FontWeight weight = medium, + Color? color, + double? height, + }) => + _token(9, weight, color, height); + + // ─── Material TextTheme mapping ──────────────────────────────────────────── + + /// Maps design tokens onto Material slots used across the app. + /// + /// | Material slot | Token | Default weight | + /// |-----------------|-----------|----------------| + /// | displayLarge | Heading-1 | SemiBold | + /// | displayMedium | Heading-2 | SemiBold | + /// | displaySmall | Heading-3 | SemiBold | + /// | headlineLarge | Heading-4 | SemiBold | + /// | headlineMedium | Heading-5 | SemiBold | + /// | headlineSmall | Heading-6 | SemiBold | + /// | titleLarge | Heading-5 | SemiBold | + /// | titleMedium | Label-1 | Medium | + /// | titleSmall | Label-2 | Medium | + /// | bodyLarge | Body-1 | Regular | + /// | bodyMedium | Body-2 | Regular | + /// | bodySmall | Body-3 | Regular | + /// | labelLarge | Label-2 | Medium | + /// | labelMedium | Label-3 | Medium | + /// | labelSmall | Caption-1 | Medium | + /// + /// Use [body4], [caption2], or weight variants via the named helpers above. + static TextTheme textTheme(ColorScheme colorScheme) { final onSurface = colorScheme.onSurface; final onSurfaceVariant = colorScheme.onSurfaceVariant; return TextTheme( - // Display - displayLarge: token(size: 57, weight: semiBold, color: onSurface), - displayMedium: token(size: 45, weight: semiBold, color: onSurface), - displaySmall: token(size: 36, weight: semiBold, color: onSurface), - // Headline - headlineLarge: token(size: 20, weight: semiBold, color: onSurface), - headlineMedium: token(size: 16, weight: regular, color: onSurface), - headlineSmall: token(size: 18, weight: semiBold, color: onSurface), - // Title - titleLarge: token(size: 22, weight: semiBold, color: onSurface), - titleMedium: token(size: 16, weight: medium, color: onSurface), - titleSmall: token(size: 14, weight: medium, color: onSurface), - // Body - bodyLarge: token(size: 16, weight: regular, color: onSurface), - bodyMedium: token(size: 16, weight: regular, color: onSurface), - bodySmall: token(size: 12, weight: regular, color: onSurfaceVariant), - // Label - labelLarge: token(size: 14, weight: medium, color: onSurface), - labelMedium: token(size: 12, weight: medium, color: onSurfaceVariant), - labelSmall: token(size: 10, weight: regular, color: onSurfaceVariant), + displayLarge: heading1(color: onSurface), + displayMedium: heading2(color: onSurface), + displaySmall: heading3(color: onSurface), + headlineLarge: heading4(color: onSurface), + headlineMedium: heading5(color: onSurface), + headlineSmall: heading6(color: onSurface), + titleLarge: heading5(color: onSurface), + titleMedium: label1(color: onSurface), + titleSmall: label2(color: onSurface), + bodyLarge: body1(color: onSurface), + bodyMedium: body2(color: onSurface), + bodySmall: body3(color: onSurfaceVariant), + labelLarge: label2(color: onSurface), + labelMedium: label3(color: onSurfaceVariant), + labelSmall: caption1(color: onSurfaceVariant), + ); + } + + static TextStyle _token( + double size, + FontWeight weight, + Color? color, + double? height, + ) { + return GoogleFonts.figtree( + fontSize: size, + fontWeight: weight, + letterSpacing: 0, + height: height, + color: color, ); } } diff --git a/lib/modules/assets/presentation/screens/asset_alerts_screen.dart b/lib/modules/assets/presentation/screens/asset_alerts_screen.dart index d6fb217..0b77cf6 100644 --- a/lib/modules/assets/presentation/screens/asset_alerts_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_alerts_screen.dart @@ -346,7 +346,6 @@ class _AlertCard extends StatelessWidget { style: theme.textTheme.bodySmall?.copyWith( color: visual.color, fontWeight: FontWeight.w600, - fontSize: 12, ), ), ), @@ -374,7 +373,6 @@ class _AlertCard extends StatelessWidget { style: theme.textTheme.bodySmall?.copyWith( color: visual.color, fontWeight: FontWeight.w600, - fontSize: 12, ), ), ), diff --git a/lib/modules/assets/presentation/screens/asset_form_screen.dart b/lib/modules/assets/presentation/screens/asset_form_screen.dart index 2a3654b..3263d40 100644 --- a/lib/modules/assets/presentation/screens/asset_form_screen.dart +++ b/lib/modules/assets/presentation/screens/asset_form_screen.dart @@ -5,12 +5,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; +import '../../../../core/constants/enums.dart'; import '../../../../core/constants/route_constants.dart'; import '../../../../core/errors/failure.dart'; import '../../../../core/utils/formatters.dart'; import '../../../../core/utils/validators.dart'; import '../../../../shared/models/asset_model.dart'; import '../../../../shared/models/user_management_models.dart' show FilterOptionModel; +import '../../../../shared/providers/permissions_provider.dart'; import '../../../../shared/utils/navigation_utils.dart'; import '../../../../shared/widgets/app_button.dart'; import '../../../../shared/widgets/app_date_popup.dart'; @@ -22,8 +24,10 @@ import '../../../../shared/widgets/app_side_panel.dart'; import '../../../../shared/widgets/app_sticky_form_layout.dart'; import '../../../../shared/widgets/app_text_field.dart'; import '../../../../shared/widgets/app_toast.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/error_view.dart'; import '../../../master_data/presentation/widgets/master_quick_add.dart'; +import '../../../vendors/presentation/widgets/vendor_form_panel.dart'; import '../../data/repositories/asset_repository_impl.dart'; import '../providers/asset_categories_provider.dart'; import '../providers/asset_form_lookups_provider.dart'; @@ -486,7 +490,7 @@ class _AssetFormScreenState extends ConsumerState { if (mounted) { showAppToastFromSnackBar( context, - SnackBar(content: Text(e.toString())), + SnackBar(content: Text(errorDisplayMessage(e))), ); } } finally { @@ -781,12 +785,7 @@ class _AssetFormScreenState extends ConsumerState { children: [ FormRowFour( children: [ - _optionalLookupDropdown( - label: 'Vendor', - value: _vendorId, - options: lookups.vendors, - onChanged: (v) => setState(() => _vendorId = v), - ), + _vendorDropdown(lookups.vendors), _optionalLookupDropdown( label: 'Purchase Order', value: _poId, @@ -1336,6 +1335,7 @@ class _AssetFormScreenState extends ConsumerState { searchHint: 'Search ${label.toLowerCase()}...', isDense: true, enabled: fieldEnabled, + openInSidePanel: true, options: dropdownOptions, refreshLookups: () { ref.invalidate(assetFormLookupsProvider); @@ -1371,6 +1371,7 @@ class _AssetFormScreenState extends ConsumerState { searchHint: 'Search subcategory...', isDense: true, enabled: hasCategory, + openInSidePanel: true, options: options, initialValues: {'item_category_id': _categoryId}, refreshLookups: () { @@ -1395,7 +1396,9 @@ class _AssetFormScreenState extends ConsumerState { value: _dropdownValue(_categoryId, categoryIds), searchHint: 'Search category...', isDense: true, + openInSidePanel: true, initialValues: const {'category_type': 'ASSET'}, + readOnlyFields: const {'category_type'}, options: activeCategories .map( (c) => AppDropdownOption( @@ -1432,16 +1435,56 @@ class _AssetFormScreenState extends ConsumerState { ); } + Widget _vendorDropdown(List vendors) { + final vendorIds = vendors + .map((vendor) => int.tryParse(vendor.id)) + .whereType() + .toList(); + final canCreateVendor = ref.can('vendors', PermissionAction.create); + return AppSearchableDropdown( + label: 'Vendor', + value: _dropdownValue(_vendorId, vendorIds), + hint: 'None', + searchHint: 'Search vendor...', + isDense: true, + options: >[ + const AppDropdownOption(value: null, label: 'None'), + ...vendors + .map( + (vendor) => AppDropdownOption( + value: int.tryParse(vendor.id), + label: vendor.name, + ), + ) + .where((option) => option.value != null && option.value! > 0), + ], + onChanged: (v) => setState(() => _vendorId = v), + addNewLabel: canCreateVendor ? 'Add vendor' : null, + onAddNew: !canCreateVendor + ? null + : () async { + final createdId = await openVendorFormPanel(context, ref); + if (!mounted || createdId == null) return; + ref.invalidate(assetFormLookupsProvider); + await ref.read(assetFormLookupsProvider.future); + if (!mounted) return; + setState(() => _vendorId = int.tryParse(createdId)); + }, + ); + } + Widget _locationDropdown(List locations) { final locationIds = locations .map((location) => int.tryParse(location.id)) .whereType() .toList(); - return AppSearchableDropdown( + return MasterQuickAddDropdown( + masterId: 'locations', label: 'Location *', value: _dropdownValue(_locationId, locationIds), searchHint: 'Search plant or warehouse...', isDense: true, + openInSidePanel: true, options: locations .map( (location) => AppDropdownOption( @@ -1451,6 +1494,11 @@ class _AssetFormScreenState extends ConsumerState { ) .where((option) => option.value != 0) .toList(), + refreshLookups: () async { + ref.invalidate(assetFormLookupsProvider); + await ref.read(assetFormLookupsProvider.future); + }, + parseCreatedId: int.tryParse, onChanged: (v) => setState(() => _locationId = v), validator: (v) => v == null ? 'Location is required' : null, ); diff --git a/lib/modules/assets/presentation/widgets/asset_maintenance_panel.dart b/lib/modules/assets/presentation/widgets/asset_maintenance_panel.dart index 8ae2358..1ad64e7 100644 --- a/lib/modules/assets/presentation/widgets/asset_maintenance_panel.dart +++ b/lib/modules/assets/presentation/widgets/asset_maintenance_panel.dart @@ -4,6 +4,7 @@ import 'package:intl/intl.dart'; import '../../../../core/utils/formatters.dart'; import '../../../../shared/models/asset_model.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_button.dart'; import '../../../../shared/widgets/app_date_popup.dart'; import '../../../../shared/widgets/app_dropdown.dart'; @@ -156,7 +157,7 @@ class _SubmitMaintenanceLogPanelState if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - showSidePanelSnackBar(context, e.toString()); + showSidePanelApiError(context, e); } } finally { if (mounted) setState(() => _isSubmitting = false); diff --git a/lib/modules/assets/presentation/widgets/asset_side_panels.dart b/lib/modules/assets/presentation/widgets/asset_side_panels.dart index c5f1d63..473bb16 100644 --- a/lib/modules/assets/presentation/widgets/asset_side_panels.dart +++ b/lib/modules/assets/presentation/widgets/asset_side_panels.dart @@ -207,7 +207,7 @@ class _AddAmcPanelState extends ConsumerState { if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - showSidePanelSnackBar(context, e.toString()); + showSidePanelApiError(context, e); } } finally { if (mounted) setState(() => _isSubmitting = false); @@ -620,7 +620,7 @@ class _LogServiceVisitPanelState extends ConsumerState { if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - showSidePanelSnackBar(context, e.toString()); + showSidePanelApiError(context, e); } } finally { if (mounted) setState(() => _isSubmitting = false); @@ -1073,7 +1073,7 @@ class _AddInsurancePanelState extends ConsumerState { if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - showSidePanelSnackBar(context, e.toString()); + showSidePanelApiError(context, e); } } finally { if (mounted) setState(() => _isSubmitting = false); diff --git a/lib/modules/auth/presentation/screens/change_password_screen.dart b/lib/modules/auth/presentation/screens/change_password_screen.dart index 42e7a0c..d7745d0 100644 --- a/lib/modules/auth/presentation/screens/change_password_screen.dart +++ b/lib/modules/auth/presentation/screens/change_password_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:google_fonts/google_fonts.dart'; +import '../../../../core/theme/app_typography.dart'; import '../../../../core/constants/route_constants.dart'; import '../../../../core/utils/validators.dart'; @@ -78,7 +78,7 @@ class _ChangePasswordScreenState extends ConsumerState { if (result.failure != null) { setState( () => _errorMessage = - result.failure?.message ?? result.failure.toString(), + result.failure?.message ?? 'Unable to change password.', ); return; } @@ -108,13 +108,9 @@ class _ChangePasswordScreenState extends ConsumerState { return InputDecoration( labelText: label, floatingLabelBehavior: FloatingLabelBehavior.auto, - labelStyle: GoogleFonts.inter( - fontSize: 14.5, - color: colors.onSurfaceVariant, - ), - floatingLabelStyle: GoogleFonts.inter( - fontSize: 12, - fontWeight: FontWeight.w600, + labelStyle: AppTypography.label2(color: colors.onSurfaceVariant), + floatingLabelStyle: AppTypography.label3( + weight: AppTypography.semiBold, color: colors.primary, ), prefixIcon: Icon(icon, color: colors.iconMuted, size: 18), @@ -212,9 +208,8 @@ class _ChangePasswordScreenState extends ConsumerState { Expanded( child: Text( 'Change Password', - style: GoogleFonts.manrope( - fontSize: 18, - fontWeight: FontWeight.w700, + style: AppTypography.body1( + weight: AppTypography.bold, color: colors.headingColor, ), ), @@ -285,21 +280,17 @@ class _ChangePasswordScreenState extends ConsumerState { const SizedBox(height: 20), Text( 'Update your password', - style: GoogleFonts.manrope( - fontSize: 24, - fontWeight: FontWeight.w800, - letterSpacing: -0.3, + style: AppTypography.heading5( + weight: AppTypography.bold, color: colors.headingColor, ), ), const SizedBox(height: 5), Text( 'Enter your current password and choose a new one. You’ll need to sign in again afterward.', - style: GoogleFonts.inter( - fontSize: 13.5, - height: 1.45, + style: AppTypography.body3( color: colors.subtitleColor, - ), + ).copyWith(height: 1.45), ), const SizedBox(height: 26), TextFormField( @@ -312,8 +303,8 @@ class _ChangePasswordScreenState extends ConsumerState { v, fieldName: 'Current password', ), - style: GoogleFonts.inter( - fontSize: 14.5, + style: AppTypography.body3( + weight: AppTypography.medium, color: colors.headingColor, ), decoration: _fieldDecoration( @@ -338,8 +329,8 @@ class _ChangePasswordScreenState extends ConsumerState { AutofillHints.newPassword ], validator: Validators.password, - style: GoogleFonts.inter( - fontSize: 14.5, + style: AppTypography.body3( + weight: AppTypography.medium, color: colors.headingColor, ), decoration: _fieldDecoration( @@ -368,8 +359,8 @@ class _ChangePasswordScreenState extends ConsumerState { } return Validators.password(v); }, - style: GoogleFonts.inter( - fontSize: 14.5, + style: AppTypography.body3( + weight: AppTypography.medium, color: colors.headingColor, ), decoration: _fieldDecoration( @@ -390,8 +381,7 @@ class _ChangePasswordScreenState extends ConsumerState { const SizedBox(height: 12), Text( _errorMessage!, - style: GoogleFonts.inter( - fontSize: 13, + style: AppTypography.body3( color: colors.colorScheme.error, ), ), @@ -415,10 +405,8 @@ class _ChangePasswordScreenState extends ConsumerState { borderRadius: BorderRadius.circular(12), ), - textStyle: GoogleFonts.inter( - fontSize: 15, - fontWeight: FontWeight.w700, - letterSpacing: 0.2, + textStyle: AppTypography.label1( + weight: AppTypography.bold, ), ), ), @@ -446,11 +434,9 @@ class _ChangePasswordScreenState extends ConsumerState { ), child: Text( 'SECURE ACCESS', - style: GoogleFonts.inter( - fontSize: 11.5, - letterSpacing: 0.6, + style: AppTypography.caption1( color: colors.onSurfaceVariant, - ), + ).copyWith(letterSpacing: 0.6), ), ), Expanded( @@ -473,8 +459,7 @@ class _ChangePasswordScreenState extends ConsumerState { const SizedBox(width: 6), Text( 'Your data is protected and encrypted', - style: GoogleFonts.inter( - fontSize: 12, + style: AppTypography.body4( color: colors.onSurfaceVariant, ), ), diff --git a/lib/modules/auth/presentation/screens/forgot_password_screen.dart b/lib/modules/auth/presentation/screens/forgot_password_screen.dart index c80a7d7..bbf19d3 100644 --- a/lib/modules/auth/presentation/screens/forgot_password_screen.dart +++ b/lib/modules/auth/presentation/screens/forgot_password_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/constants/route_constants.dart'; +import '../../../../core/theme/app_typography.dart'; import '../../../../core/utils/validators.dart'; import '../../../../shared/widgets/app_button.dart'; import '../../../../shared/widgets/app_text_field.dart'; @@ -47,7 +48,10 @@ class _ForgotPasswordScreenState extends ConsumerState { setState(() => _isLoading = false); if (result.failure != null) { - setState(() => _errorMessage = result.failure.toString()); + setState( + () => _errorMessage = + result.failure?.message ?? 'Unable to send reset email.', + ); return; } @@ -86,7 +90,9 @@ class _ForgotPasswordScreenState extends ConsumerState { const SizedBox(height: 12), Text( _errorMessage!, - style: TextStyle(color: Theme.of(context).colorScheme.error), + style: AppTypography.body3( + color: Theme.of(context).colorScheme.error, + ), textAlign: TextAlign.center, ), ], @@ -94,7 +100,9 @@ class _ForgotPasswordScreenState extends ConsumerState { const SizedBox(height: 12), Text( _successMessage!, - style: TextStyle(color: Theme.of(context).colorScheme.primary), + style: AppTypography.body3( + color: Theme.of(context).colorScheme.primary, + ), textAlign: TextAlign.center, ), ], diff --git a/lib/modules/auth/presentation/screens/login_screen.dart b/lib/modules/auth/presentation/screens/login_screen.dart index 60d76c2..6a84e66 100644 --- a/lib/modules/auth/presentation/screens/login_screen.dart +++ b/lib/modules/auth/presentation/screens/login_screen.dart @@ -3,7 +3,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:google_fonts/google_fonts.dart'; +import '../../../../core/theme/app_typography.dart'; import '../../../../core/config/dev_config.dart'; import '../../../../core/constants/enums.dart'; @@ -171,7 +171,10 @@ class _LoginScreenState extends ConsumerState setState(() => _isForgotLoading = false); if (result.failure != null) { - setState(() => _forgotErrorMessage = result.failure.toString()); + setState( + () => _forgotErrorMessage = + result.failure?.message ?? 'Unable to send reset email.', + ); return; } @@ -213,7 +216,7 @@ class _LoginScreenState extends ConsumerState if (result.failure != null) { setState( () => _resetErrorMessage = - result.failure?.message ?? result.failure.toString(), + result.failure?.message ?? 'Unable to reset password.', ); return; } @@ -295,13 +298,9 @@ class _LoginScreenState extends ConsumerState return InputDecoration( labelText: label, floatingLabelBehavior: FloatingLabelBehavior.auto, - labelStyle: GoogleFonts.inter( - fontSize: 14.5, - color: colors.onSurfaceVariant, - ), - floatingLabelStyle: GoogleFonts.inter( - fontSize: 12, - fontWeight: FontWeight.w600, + labelStyle: AppTypography.label2(color: colors.onSurfaceVariant), + floatingLabelStyle: AppTypography.label3( + weight: AppTypography.semiBold, color: colors.primary, ), prefixIcon: Icon(icon, color: colors.iconMuted, size: 18), @@ -387,11 +386,8 @@ class _LoginScreenState extends ConsumerState padding: const EdgeInsets.symmetric(horizontal: 10), child: Text( 'SECURE ACCESS', - style: GoogleFonts.inter( - fontSize: 11.5, - letterSpacing: 0.6, - color: colors.onSurfaceVariant, - ), + style: AppTypography.caption1(color: colors.onSurfaceVariant) + .copyWith(letterSpacing: 0.6), ), ), Expanded(child: Divider(color: colors.outlineSoft, height: 1)), @@ -409,10 +405,7 @@ class _LoginScreenState extends ConsumerState const SizedBox(width: 6), Text( 'Your data is protected and encrypted', - style: GoogleFonts.inter( - fontSize: 12, - color: colors.onSurfaceVariant, - ), + style: AppTypography.body4(color: colors.onSurfaceVariant), ), ], ), @@ -430,10 +423,7 @@ class _LoginScreenState extends ConsumerState ), child: Text( 'Back to Sign In', - style: GoogleFonts.inter( - fontSize: 13.5, - fontWeight: FontWeight.w600, - ), + style: AppTypography.body3(weight: AppTypography.semiBold), ), ), ); @@ -456,11 +446,7 @@ class _LoginScreenState extends ConsumerState shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), - textStyle: GoogleFonts.inter( - fontSize: 15, - fontWeight: FontWeight.w700, - letterSpacing: 0.2, - ), + textStyle: AppTypography.label1(weight: AppTypography.bold), ), ), ), @@ -483,20 +469,15 @@ class _LoginScreenState extends ConsumerState const SizedBox(height: 20), Text( 'Welcome', - style: GoogleFonts.manrope( - fontSize: 24, - fontWeight: FontWeight.w800, - letterSpacing: -0.3, + style: AppTypography.heading5( + weight: AppTypography.bold, color: colors.headingColor, ), ), const SizedBox(height: 5), Text( 'Sign in to access your BCPL workspace.', - style: GoogleFonts.inter( - fontSize: 13.5, - color: colors.subtitleColor, - ), + style: AppTypography.body3(color: colors.subtitleColor), ), const SizedBox(height: 26), TextFormField( @@ -504,8 +485,8 @@ class _LoginScreenState extends ConsumerState keyboardType: TextInputType.emailAddress, autofillHints: const [AutofillHints.email], validator: Validators.email, - style: GoogleFonts.inter( - fontSize: 14.5, + style: AppTypography.body3( + weight: AppTypography.medium, color: colors.headingColor, ), decoration: _fieldDecoration( @@ -520,8 +501,8 @@ class _LoginScreenState extends ConsumerState obscureText: _obscurePassword, autofillHints: const [AutofillHints.password], validator: (v) => Validators.required(v, fieldName: 'Password'), - style: GoogleFonts.inter( - fontSize: 14.5, + style: AppTypography.body3( + weight: AppTypography.medium, color: colors.headingColor, ), decoration: _fieldDecoration( @@ -561,10 +542,7 @@ class _LoginScreenState extends ConsumerState ), Text( 'Remember me', - style: GoogleFonts.inter( - fontSize: 13, - color: colors.labelColor, - ), + style: AppTypography.body3(color: colors.labelColor), ), const Spacer(), TextButton( @@ -577,9 +555,8 @@ class _LoginScreenState extends ConsumerState ), child: Text( 'Forgot password?', - style: GoogleFonts.inter( - fontSize: 13, - fontWeight: FontWeight.w600, + style: AppTypography.body3( + weight: AppTypography.semiBold, ), ), ), @@ -603,10 +580,7 @@ class _LoginScreenState extends ConsumerState Text( 'Login API unavailable? Browse all screens without signing in:', textAlign: TextAlign.center, - style: GoogleFonts.inter( - fontSize: 12, - color: colors.subtitleColor, - ), + style: AppTypography.body4(color: colors.subtitleColor), ), const SizedBox(height: 12), Theme( @@ -653,21 +627,16 @@ class _LoginScreenState extends ConsumerState const SizedBox(height: 20), Text( 'Forgot Password', - style: GoogleFonts.manrope( - fontSize: 24, - fontWeight: FontWeight.w800, - letterSpacing: -0.3, + style: AppTypography.heading5( + weight: AppTypography.bold, color: colors.headingColor, ), ), const SizedBox(height: 5), Text( 'Enter your registered email address to receive password reset instructions.', - style: GoogleFonts.inter( - fontSize: 13.5, - height: 1.45, - color: colors.subtitleColor, - ), + style: AppTypography.body3(color: colors.subtitleColor) + .copyWith(height: 1.45), ), const SizedBox(height: 26), TextFormField( @@ -675,8 +644,8 @@ class _LoginScreenState extends ConsumerState keyboardType: TextInputType.emailAddress, autofillHints: const [AutofillHints.email], validator: Validators.email, - style: GoogleFonts.inter( - fontSize: 14.5, + style: AppTypography.body3( + weight: AppTypography.medium, color: colors.headingColor, ), decoration: _fieldDecoration( @@ -689,20 +658,14 @@ class _LoginScreenState extends ConsumerState const SizedBox(height: 12), Text( _forgotErrorMessage!, - style: GoogleFonts.inter( - fontSize: 13, - color: colors.colorScheme.error, - ), + style: AppTypography.body3(color: colors.colorScheme.error), ), ], if (_forgotSuccessMessage != null) ...[ const SizedBox(height: 12), Text( _forgotSuccessMessage!, - style: GoogleFonts.inter( - fontSize: 13, - color: colors.primary, - ), + style: AppTypography.body3(color: colors.primary), ), ], const SizedBox(height: 18), @@ -741,21 +704,16 @@ class _LoginScreenState extends ConsumerState const SizedBox(height: 20), Text( 'Reset Password', - style: GoogleFonts.manrope( - fontSize: 24, - fontWeight: FontWeight.w800, - letterSpacing: -0.3, + style: AppTypography.heading5( + weight: AppTypography.bold, color: colors.headingColor, ), ), const SizedBox(height: 5), Text( 'Choose a new password for your account.', - style: GoogleFonts.inter( - fontSize: 13.5, - height: 1.45, - color: colors.subtitleColor, - ), + style: AppTypography.body3(color: colors.subtitleColor) + .copyWith(height: 1.45), ), const SizedBox(height: 26), TextFormField( @@ -763,8 +721,8 @@ class _LoginScreenState extends ConsumerState obscureText: _obscureNewPassword, autofillHints: const [AutofillHints.newPassword], validator: Validators.password, - style: GoogleFonts.inter( - fontSize: 14.5, + style: AppTypography.body3( + weight: AppTypography.medium, color: colors.headingColor, ), decoration: _fieldDecoration( @@ -798,8 +756,8 @@ class _LoginScreenState extends ConsumerState } return Validators.password(v); }, - style: GoogleFonts.inter( - fontSize: 14.5, + style: AppTypography.body3( + weight: AppTypography.medium, color: colors.headingColor, ), decoration: _fieldDecoration( @@ -827,20 +785,14 @@ class _LoginScreenState extends ConsumerState const SizedBox(height: 12), Text( _resetErrorMessage!, - style: GoogleFonts.inter( - fontSize: 13, - color: colors.colorScheme.error, - ), + style: AppTypography.body3(color: colors.colorScheme.error), ), ], if ((_resetToken ?? '').isEmpty) ...[ const SizedBox(height: 12), Text( 'This reset link is invalid or has expired. Request a new one from Forgot Password.', - style: GoogleFonts.inter( - fontSize: 13, - color: colors.colorScheme.error, - ), + style: AppTypography.body3(color: colors.colorScheme.error), ), ], const SizedBox(height: 18), diff --git a/lib/modules/auth/presentation/widgets/bcpl_logo.dart b/lib/modules/auth/presentation/widgets/bcpl_logo.dart index 0d4687c..eb5a5a3 100644 --- a/lib/modules/auth/presentation/widgets/bcpl_logo.dart +++ b/lib/modules/auth/presentation/widgets/bcpl_logo.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../../../core/theme/app_typography.dart'; import 'login_colors.dart'; /// BCPL wordmark with blue and green swooshes, matching the login design. @@ -26,8 +27,9 @@ class BcplLogo extends StatelessWidget { child: Text( 'BCPL', style: TextStyle( + fontFamily: AppTypography.fontFamily, fontSize: height * 0.58, - fontWeight: FontWeight.w800, + fontWeight: AppTypography.bold, fontStyle: FontStyle.italic, letterSpacing: -0.5, color: colors.headingColor, diff --git a/lib/modules/auth/presentation/widgets/login_hero_illustration.dart b/lib/modules/auth/presentation/widgets/login_hero_illustration.dart index 3f2bb66..76e42e3 100644 --- a/lib/modules/auth/presentation/widgets/login_hero_illustration.dart +++ b/lib/modules/auth/presentation/widgets/login_hero_illustration.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'login_colors.dart'; +import '../../../../core/theme/app_typography.dart'; /// Orbiting ERP module illustration for the login brand panel. class LoginHeroIllustration extends StatefulWidget { @@ -200,12 +201,10 @@ class _Hub extends StatelessWidget { ), child: Text( 'ERP', - style: TextStyle( + style: AppTypography.body2( + weight: AppTypography.bold, color: colors.panelText, - fontWeight: FontWeight.w800, - fontSize: 17, - letterSpacing: 0.5, - ), + ).copyWith(letterSpacing: 0.5), ), ); } diff --git a/lib/modules/auth/presentation/widgets/login_hero_panel.dart b/lib/modules/auth/presentation/widgets/login_hero_panel.dart index db7a898..b8e8614 100644 --- a/lib/modules/auth/presentation/widgets/login_hero_panel.dart +++ b/lib/modules/auth/presentation/widgets/login_hero_panel.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; +import '../../../../core/theme/app_typography.dart'; import 'login_colors.dart'; import 'login_hero_illustration.dart'; @@ -40,24 +40,26 @@ class LoginHeroPanel extends StatelessWidget { SizedBox(height: compact ? 20 : 28), Text( 'Smart. Integrated.\nEfficient.', - style: GoogleFonts.manrope( - fontSize: compact ? 28 : 40, - fontWeight: FontWeight.w800, - height: 1.12, - letterSpacing: -0.5, - color: colors.panelText, - ), + style: (compact + ? AppTypography.heading4( + weight: AppTypography.bold, + color: colors.panelText, + ) + : AppTypography.heading3( + weight: AppTypography.bold, + color: colors.panelText, + )) + .copyWith(height: 1.12), ), const SizedBox(height: 14), ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: Text( 'One workspace for inventory, sales, purchases, accounts and reporting — built for teams who move fast.', - style: GoogleFonts.inter( - fontSize: compact ? 13.5 : 15, - height: 1.6, - color: colors.panelTextDim, - ), + style: (compact + ? AppTypography.body3(color: colors.panelTextDim) + : AppTypography.body2(color: colors.panelTextDim)) + .copyWith(height: 1.6), ), ), SizedBox(height: compact ? 16 : 24), @@ -89,20 +91,15 @@ class _BrandMark extends StatelessWidget { children: [ Text( 'BCPL', - style: GoogleFonts.manrope( - fontWeight: FontWeight.w800, - fontSize: 20, - letterSpacing: 0.5, + style: AppTypography.heading6( + weight: AppTypography.bold, color: colors.panelText, - ), + ).copyWith(letterSpacing: 0.5), ), Text( 'BHARAT ERP', - style: GoogleFonts.inter( - fontSize: 11, - letterSpacing: 1.5, - color: colors.panelTextDim, - ), + style: AppTypography.caption1(color: colors.panelTextDim) + .copyWith(letterSpacing: 1.5), ), ], ); @@ -170,9 +167,8 @@ class _FeatureItem extends StatelessWidget { const SizedBox(width: 10), Text( title, - style: GoogleFonts.inter( - fontSize: 12.5, - fontWeight: FontWeight.w600, + style: AppTypography.label3( + weight: AppTypography.semiBold, color: colors.panelText, ), ), diff --git a/lib/modules/grn/presentation/screens/grn_detail_screen.dart b/lib/modules/grn/presentation/screens/grn_detail_screen.dart index eca753e..237d98a 100644 --- a/lib/modules/grn/presentation/screens/grn_detail_screen.dart +++ b/lib/modules/grn/presentation/screens/grn_detail_screen.dart @@ -18,6 +18,7 @@ import '../providers/grn_provider.dart'; import '../widgets/grn_attachments_card.dart'; import '../widgets/grn_line_items_editor.dart'; import '../widgets/grn_status_chip.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_toast.dart'; class GrnDetailScreen extends ConsumerStatefulWidget { @@ -134,7 +135,10 @@ class _GrnDetailScreenState extends ConsumerState { } } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isWorking = false); @@ -202,7 +206,10 @@ class _GrnDetailScreenState extends ConsumerState { } } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isDownloadingPdf = false); diff --git a/lib/modules/grn/presentation/screens/grn_form_screen.dart b/lib/modules/grn/presentation/screens/grn_form_screen.dart index ae35512..f85716c 100644 --- a/lib/modules/grn/presentation/screens/grn_form_screen.dart +++ b/lib/modules/grn/presentation/screens/grn_form_screen.dart @@ -5,13 +5,13 @@ import 'package:go_router/go_router.dart'; import '../../../../core/constants/route_constants.dart'; import '../../../../core/errors/failure.dart'; -import '../../../../core/network/api_handler.dart'; import '../../../../core/utils/formatters.dart'; import '../../../../core/utils/validators.dart'; import '../../../../shared/models/grn_model.dart'; import '../../../../shared/models/purchase_order_model.dart'; import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/utils/navigation_utils.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_button.dart'; import '../../../../shared/widgets/app_date_popup.dart'; import '../../../../shared/widgets/app_dropdown.dart'; @@ -301,9 +301,10 @@ class _GrnFormScreenState extends ConsumerState { }); } catch (e) { if (!mounted) return; - final message = - e is Failure ? validationErrorMessage(e) : e.toString(); - showAppToastFromSnackBar(context, SnackBar(content: Text(message))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } finally { if (mounted) setState(() => _isSubmitting = false); } @@ -463,8 +464,11 @@ class _GrnFormScreenState extends ConsumerState { } } catch (e) { if (!mounted) return; - showAppToastFromSnackBar(context, - SnackBar(content: Text(e.toString())), + showAppToastFromSnackBar( + context, + SnackBar( + content: Text(errorDisplayMessage(e)), + ), ); } }, diff --git a/lib/modules/grn/presentation/widgets/grn_attachments_card.dart b/lib/modules/grn/presentation/widgets/grn_attachments_card.dart index 435e2fe..8d24927 100644 --- a/lib/modules/grn/presentation/widgets/grn_attachments_card.dart +++ b/lib/modules/grn/presentation/widgets/grn_attachments_card.dart @@ -2,11 +2,10 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../../core/errors/failure.dart'; -import '../../../../core/network/api_handler.dart'; import '../../../../core/utils/formatters.dart'; import '../../../../shared/models/grn_model.dart'; import '../../../../shared/utils/file_download_helper.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_confirmation_dialog.dart'; import '../providers/grn_provider.dart'; import '../../../../shared/widgets/app_toast.dart'; @@ -95,10 +94,9 @@ class _GrnAttachmentsCardState extends ConsumerState { ); } catch (e) { if (!mounted) return; - final message = - e is Failure ? validationErrorMessage(e) : e.toString(); - showAppToastFromSnackBar(context, - SnackBar(content: Text(message)), + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), ); } finally { if (mounted) setState(() => _isUploading = false); @@ -118,8 +116,9 @@ class _GrnAttachmentsCardState extends ConsumerState { ); } catch (e) { if (!mounted) return; - showAppToastFromSnackBar(context, - SnackBar(content: Text(e.toString())), + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), ); } finally { if (mounted) setState(() => _busyAttachmentId = null); @@ -150,10 +149,9 @@ class _GrnAttachmentsCardState extends ConsumerState { ); } catch (e) { if (!mounted) return; - final message = - e is Failure ? validationErrorMessage(e) : e.toString(); - showAppToastFromSnackBar(context, - SnackBar(content: Text(message)), + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), ); } finally { if (mounted) setState(() => _busyAttachmentId = null); diff --git a/lib/modules/grn/presentation/widgets/grn_status_chip.dart b/lib/modules/grn/presentation/widgets/grn_status_chip.dart index 8d88fdc..30f6035 100644 --- a/lib/modules/grn/presentation/widgets/grn_status_chip.dart +++ b/lib/modules/grn/presentation/widgets/grn_status_chip.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../../../core/theme/app_typography.dart'; import '../../../../shared/models/grn_model.dart'; import '../../../../shared/widgets/app_status_chip.dart'; @@ -31,11 +32,15 @@ class GrnStatusChip extends StatelessWidget { return Chip( label: Text( label, - style: TextStyle( - color: color, - fontSize: compact ? 11 : 12, - fontWeight: FontWeight.w600, - ), + style: compact + ? AppTypography.caption1( + weight: AppTypography.semiBold, + color: color, + ) + : AppTypography.label3( + weight: AppTypography.semiBold, + color: color, + ), ), backgroundColor: color.withValues(alpha: 0.12), side: BorderSide(color: color.withValues(alpha: 0.3)), diff --git a/lib/modules/master_data/presentation/screens/masters_hub_screen.dart b/lib/modules/master_data/presentation/screens/masters_hub_screen.dart index 2f0f972..a7e4020 100644 --- a/lib/modules/master_data/presentation/screens/masters_hub_screen.dart +++ b/lib/modules/master_data/presentation/screens/masters_hub_screen.dart @@ -142,7 +142,6 @@ class _MasterAppTile extends StatelessWidget { style: theme.textTheme.bodySmall?.copyWith( fontWeight: FontWeight.w500, height: 1.2, - fontSize: 12, ), ), ], diff --git a/lib/modules/master_data/presentation/widgets/master_form_panel.dart b/lib/modules/master_data/presentation/widgets/master_form_panel.dart index 0e6b171..562dabc 100644 --- a/lib/modules/master_data/presentation/widgets/master_form_panel.dart +++ b/lib/modules/master_data/presentation/widgets/master_form_panel.dart @@ -22,6 +22,7 @@ class MasterFormPanel extends ConsumerStatefulWidget { this.recordId, this.initialValues, this.formSessionId, + this.readOnlyFields, }); final String masterId; @@ -33,6 +34,9 @@ class MasterFormPanel extends ConsumerStatefulWidget { /// Unique per open so create forms always start empty. final String? formSessionId; + /// Field keys rendered as read-only (in addition to [MasterFieldDef.readOnly]). + final Set? readOnlyFields; + bool get isEditing => recordId != null; @override @@ -105,6 +109,10 @@ class _MasterFormPanelState extends ConsumerState { ); } + bool _isFieldReadOnly(MasterFieldDef field) => + field.readOnly || + (widget.readOnlyFields?.contains(field.key) ?? false); + Widget _buildField( BuildContext context, { required MasterFieldDef field, @@ -152,7 +160,7 @@ class _MasterFormPanelState extends ConsumerState { ); case MasterFieldType.dropdown: - if (field.readOnly) { + if (_isFieldReadOnly(field)) { final display = field.key == 'gst_rate_id' ? (gstRateDisplayFromValues(formState.values) ?? '') : (value?.toString() ?? ''); diff --git a/lib/modules/master_data/presentation/widgets/master_quick_add.dart b/lib/modules/master_data/presentation/widgets/master_quick_add.dart index 684a704..dd5f578 100644 --- a/lib/modules/master_data/presentation/widgets/master_quick_add.dart +++ b/lib/modules/master_data/presentation/widgets/master_quick_add.dart @@ -37,6 +37,7 @@ class MasterQuickAddDropdown extends ConsumerStatefulWidget { this.refreshLookups, this.addNewLabel, this.openInSidePanel = false, + this.readOnlyFields, }); final String masterId; @@ -59,6 +60,9 @@ class MasterQuickAddDropdown extends ConsumerStatefulWidget { /// When true, Quick Add opens [MasterFormPanel] in a side panel popup. final bool openInSidePanel; + /// Field keys locked as read-only in the Quick Add form (side panel or inline). + final Set? readOnlyFields; + @override ConsumerState> createState() => _MasterQuickAddDropdownState(); @@ -117,6 +121,7 @@ class _MasterQuickAddDropdownState masterId: widget.masterId, initialValues: widget.initialValues, formSessionId: sessionId, + readOnlyFields: widget.readOnlyFields, ), width: 560, ); diff --git a/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart b/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart index 93ed66b..3f9e189 100644 --- a/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart +++ b/lib/modules/purchase_orders/presentation/screens/purchase_order_detail_screen.dart @@ -22,6 +22,7 @@ import '../providers/purchase_orders_provider.dart'; import '../../data/repositories/purchase_order_repository_impl.dart'; import '../widgets/po_status_chip.dart'; import '../widgets/purchase_order_line_items_editor.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_toast.dart'; class PurchaseOrderDetailScreen extends ConsumerStatefulWidget { @@ -171,7 +172,10 @@ class _PurchaseOrderDetailScreenState } } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isWorking = false); @@ -209,10 +213,9 @@ class _PurchaseOrderDetailScreenState } } catch (e) { if (mounted) { - final message = e is Failure ? e.message : e.toString(); showAppToastFromSnackBar( context, - SnackBar(content: Text(message)), + SnackBar(content: Text(errorDisplayMessage(e))), ); } } finally { @@ -293,7 +296,10 @@ class _PurchaseOrderDetailScreenState } } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isWorking = false); @@ -342,7 +348,10 @@ class _PurchaseOrderDetailScreenState } } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isWorking = false); @@ -362,7 +371,10 @@ class _PurchaseOrderDetailScreenState ); } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isDownloadingPdf = false); @@ -1400,7 +1412,7 @@ class _AmountSummaryCard extends StatelessWidget { Text( CurrencyFormatter.format(order.totalAmount), style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w800, + fontWeight: FontWeight.w700, color: theme.colorScheme.primary, ), ), diff --git a/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart b/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart index 9b9492e..0daa291 100644 --- a/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart +++ b/lib/modules/purchase_orders/presentation/screens/purchase_order_form_screen.dart @@ -1041,7 +1041,7 @@ class _AmountSummaryCard extends StatelessWidget { Text( CurrencyFormatter.format(totals.grandTotal), style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w800, + fontWeight: FontWeight.w700, color: theme.colorScheme.primary, ), ), @@ -1159,9 +1159,8 @@ class _SummaryInputRow extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), errorMaxLines: 2, - errorStyle: theme.textTheme.bodySmall?.copyWith( + errorStyle: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.error, - fontSize: 11, ), ), ), diff --git a/lib/modules/purchase_orders/presentation/widgets/po_status_chip.dart b/lib/modules/purchase_orders/presentation/widgets/po_status_chip.dart index 4b143ba..003443f 100644 --- a/lib/modules/purchase_orders/presentation/widgets/po_status_chip.dart +++ b/lib/modules/purchase_orders/presentation/widgets/po_status_chip.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../../../core/theme/app_typography.dart'; import '../../../../shared/models/purchase_order_model.dart'; import '../../../../shared/widgets/app_status_chip.dart'; @@ -101,13 +102,16 @@ class _PoBadge extends StatelessWidget { widthFactor: 1, child: Text( label, - style: TextStyle( - color: color, - fontSize: compact ? 11 : 12, - fontWeight: FontWeight.w600, - height: 1, - letterSpacing: 0.1, - ), + style: (compact + ? AppTypography.caption1( + weight: AppTypography.semiBold, + color: color, + ) + : AppTypography.label3( + weight: AppTypography.semiBold, + color: color, + )) + .copyWith(height: 1, letterSpacing: 0.1), ), ), ); diff --git a/lib/modules/rbac/presentation/widgets/rbac_widgets.dart b/lib/modules/rbac/presentation/widgets/rbac_widgets.dart index 3d4c074..f838367 100644 --- a/lib/modules/rbac/presentation/widgets/rbac_widgets.dart +++ b/lib/modules/rbac/presentation/widgets/rbac_widgets.dart @@ -1,6 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import '../../../../core/theme/app_typography.dart'; import '../../../../shared/widgets/app_data_table.dart'; import '../../../../shared/widgets/app_table_action_icon.dart'; @@ -278,7 +279,7 @@ class UserTableUserCell extends StatelessWidget { children: [ AppTableCell.text( user.fullName, - style: const TextStyle(fontWeight: FontWeight.w600), + style: AppTypography.label2(weight: AppTypography.semiBold), ), AppTableCell.text( user.email, @@ -325,8 +326,9 @@ class UserAvatarChip extends StatelessWidget { child: Text( display.length > 2 ? display.substring(0, 2) : display, style: TextStyle( + fontFamily: AppTypography.fontFamily, color: color, - fontWeight: FontWeight.w700, + fontWeight: AppTypography.bold, fontSize: radius * 0.6, ), ), diff --git a/lib/modules/users/presentation/providers/users_provider.dart b/lib/modules/users/presentation/providers/users_provider.dart index 9757f46..519f33e 100644 --- a/lib/modules/users/presentation/providers/users_provider.dart +++ b/lib/modules/users/presentation/providers/users_provider.dart @@ -260,7 +260,9 @@ class UsersListNotifier extends AsyncNotifier { if (result.failure != null) { final current = state.valueOrNull; if (current != null) { - state = AsyncData(current.copyWith(actionError: result.failure.toString())); + state = AsyncData( + current.copyWith(actionError: result.failure!.message), + ); } return false; } @@ -281,7 +283,9 @@ class UsersListNotifier extends AsyncNotifier { if (result.failure != null) { final current = state.valueOrNull; if (current != null) { - state = AsyncData(current.copyWith(actionError: result.failure.toString())); + state = AsyncData( + current.copyWith(actionError: result.failure!.message), + ); } return false; } diff --git a/lib/modules/users/presentation/screens/user_form_screen.dart b/lib/modules/users/presentation/screens/user_form_screen.dart index b878fbd..2ea569b 100644 --- a/lib/modules/users/presentation/screens/user_form_screen.dart +++ b/lib/modules/users/presentation/screens/user_form_screen.dart @@ -13,6 +13,7 @@ import '../../../../shared/widgets/app_text_field.dart'; import '../../../../shared/widgets/error_view.dart'; import '../../../master_data/presentation/widgets/master_quick_add.dart'; import '../providers/users_provider.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_toast.dart'; class UserFormScreen extends ConsumerStatefulWidget { @@ -144,8 +145,9 @@ class _UserFormScreenState extends ConsumerState { context.pop(); } catch (e) { if (!mounted) return; - showAppToastFromSnackBar(context, - SnackBar(content: Text(e.toString())), + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), ); } finally { if (mounted) setState(() => _isSubmitting = false); diff --git a/lib/modules/users/presentation/screens/user_profile_screen.dart b/lib/modules/users/presentation/screens/user_profile_screen.dart index cf214b7..69cb489 100644 --- a/lib/modules/users/presentation/screens/user_profile_screen.dart +++ b/lib/modules/users/presentation/screens/user_profile_screen.dart @@ -86,7 +86,7 @@ class _UserProfileScreenState extends ConsumerState { context, SnackBar( content: Text( - uploadResult.failure?.message ?? uploadResult.failure.toString(), + uploadResult.failure?.message ?? 'Unable to upload profile picture.', ), ), ); @@ -131,7 +131,9 @@ class _UserProfileScreenState extends ConsumerState { showAppToastFromSnackBar( context, SnackBar( - content: Text(result.failure?.message ?? result.failure.toString()), + content: Text( + result.failure?.message ?? 'Unable to update profile.', + ), ), ); return; diff --git a/lib/modules/vendors/presentation/screens/vendor_detail_screen.dart b/lib/modules/vendors/presentation/screens/vendor_detail_screen.dart index 96e5aa6..53eb986 100644 --- a/lib/modules/vendors/presentation/screens/vendor_detail_screen.dart +++ b/lib/modules/vendors/presentation/screens/vendor_detail_screen.dart @@ -18,6 +18,7 @@ import '../../../../shared/widgets/app_segmented_tab_bar.dart'; import '../providers/vendors_provider.dart'; import '../widgets/vendor_form_panel.dart'; import '../widgets/vendor_sub_resource_panels.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_toast.dart'; class VendorDetailScreen extends ConsumerStatefulWidget { @@ -209,7 +210,10 @@ class _VendorDetailScreenState extends ConsumerState } } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } } @@ -229,7 +233,10 @@ class _VendorDetailScreenState extends ConsumerState if (mounted) context.go(RouteConstants.vendors); } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } } @@ -482,7 +489,10 @@ class _AddressesTab extends ConsumerWidget { await ref.read(vendorDetailProvider(vendorId).notifier).deleteAddress(addressId); } catch (e) { if (context.mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } } @@ -582,7 +592,10 @@ class _ContactsTab extends ConsumerWidget { await ref.read(vendorDetailProvider(vendorId).notifier).deleteContact(contactId); } catch (e) { if (context.mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } } @@ -725,7 +738,10 @@ class _BankDetailsTab extends ConsumerWidget { ); } catch (e) { if (context.mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } } @@ -750,7 +766,10 @@ class _BankDetailsTab extends ConsumerWidget { .deleteBankDetail(bankDetailId); } catch (e) { if (context.mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } } diff --git a/lib/modules/vendors/presentation/widgets/vendor_form_panel.dart b/lib/modules/vendors/presentation/widgets/vendor_form_panel.dart index 3a90aef..3cd19f8 100644 --- a/lib/modules/vendors/presentation/widgets/vendor_form_panel.dart +++ b/lib/modules/vendors/presentation/widgets/vendor_form_panel.dart @@ -16,6 +16,7 @@ import '../../../../shared/widgets/error_view.dart'; import '../../../master_data/presentation/widgets/master_quick_add.dart'; import '../providers/vendor_lookups_provider.dart'; import '../providers/vendors_provider.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_toast.dart'; Future openVendorFormPanel( @@ -160,8 +161,9 @@ class _VendorFormPanelState extends ConsumerState { if (mounted) Navigator.of(context, rootNavigator: true).pop(savedId); } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, - SnackBar(content: Text(e.toString())), + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), ); } } finally { diff --git a/lib/modules/vendors/presentation/widgets/vendor_sub_resource_panels.dart b/lib/modules/vendors/presentation/widgets/vendor_sub_resource_panels.dart index aac75b9..19468e0 100644 --- a/lib/modules/vendors/presentation/widgets/vendor_sub_resource_panels.dart +++ b/lib/modules/vendors/presentation/widgets/vendor_sub_resource_panels.dart @@ -9,6 +9,7 @@ import '../../../../shared/widgets/app_form_toggle_field.dart'; import '../../../../shared/widgets/app_side_panel.dart'; import '../../../../shared/widgets/app_text_field.dart'; import '../providers/vendors_provider.dart'; +import '../../../../shared/widgets/api_feedback.dart'; import '../../../../shared/widgets/app_toast.dart'; Future openVendorAddressPanel( @@ -144,7 +145,10 @@ class _VendorAddressPanelState extends ConsumerState { if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isSubmitting = false); @@ -285,7 +289,10 @@ class _VendorContactPanelState extends ConsumerState { if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isSubmitting = false); @@ -436,7 +443,10 @@ class _VendorBankDetailPanelState extends ConsumerState { if (mounted) Navigator.of(context, rootNavigator: true).pop(true); } catch (e) { if (mounted) { - showAppToastFromSnackBar(context, SnackBar(content: Text(e.toString()))); + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), + ); } } finally { if (mounted) setState(() => _isSubmitting = false); diff --git a/lib/shared/widgets/api_feedback.dart b/lib/shared/widgets/api_feedback.dart index 8f54575..c4ecd46 100644 --- a/lib/shared/widgets/api_feedback.dart +++ b/lib/shared/widgets/api_feedback.dart @@ -5,6 +5,21 @@ import '../../core/network/api_handler.dart'; import 'app_side_panel.dart'; import 'app_toast.dart'; +/// User-facing text from a [Failure] or other thrown error. +/// Prefer this over [Object.toString] for toasts (never show `Failure.server(...)`). +String errorDisplayMessage( + Object error, { + String fallback = 'Something went wrong. Please try again.', +}) { + if (error is Failure) { + return error is ValidationFailure + ? validationErrorMessage(error) + : error.message; + } + final text = error.toString().trim(); + return text.isEmpty ? fallback : text; +} + void showAccessDeniedSnackBar(BuildContext context, {String? message}) { showAppToast( context, @@ -14,12 +29,7 @@ void showAccessDeniedSnackBar(BuildContext context, {String? message}) { } void showSidePanelApiError(BuildContext context, Object error) { - final message = error is Failure - ? (error is ValidationFailure - ? validationErrorMessage(error) - : error.message) - : 'Something went wrong. Please try again.'; - showSidePanelSnackBar(context, message); + showSidePanelSnackBar(context, errorDisplayMessage(error)); } void showApiFailureSnackBar(BuildContext context, Failure failure) { @@ -35,9 +45,15 @@ void showApiFailureSnackBar(BuildContext context, Failure failure) { showAppToast( context, - failure is ValidationFailure - ? validationErrorMessage(failure) - : failure.message, + errorDisplayMessage(failure), + type: AppToastType.error, + ); +} + +void showApiErrorToast(BuildContext context, Object error) { + showAppToast( + context, + errorDisplayMessage(error), type: AppToastType.error, ); } diff --git a/lib/shared/widgets/app_status_chip.dart b/lib/shared/widgets/app_status_chip.dart index 2ab9d39..f5661f9 100644 --- a/lib/shared/widgets/app_status_chip.dart +++ b/lib/shared/widgets/app_status_chip.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../../core/constants/enums.dart'; +import '../../core/theme/app_typography.dart'; /// Uniform width for status chips inside data tables (fits "Partially Received"). const double kTableStatusChipWidth = 132; @@ -38,13 +39,16 @@ class TableStatusBadge extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, - style: TextStyle( - color: color, - fontSize: compact ? 11 : 12, - fontWeight: FontWeight.w600, - height: 1, - letterSpacing: 0.1, - ), + style: (compact + ? AppTypography.caption1( + weight: AppTypography.semiBold, + color: color, + ) + : AppTypography.label3( + weight: AppTypography.semiBold, + color: color, + )) + .copyWith(height: 1, letterSpacing: 0.1), ), ), ); @@ -86,11 +90,15 @@ class AppStatusChip extends StatelessWidget { return Chip( label: Text( label, - style: TextStyle( - color: color, - fontSize: compact ? 11 : 12, - fontWeight: FontWeight.w600, - ), + style: compact + ? AppTypography.caption1( + weight: AppTypography.semiBold, + color: color, + ) + : AppTypography.label3( + weight: AppTypography.semiBold, + color: color, + ), ), backgroundColor: color.withValues(alpha: 0.12), side: BorderSide(color: color.withValues(alpha: 0.3)), diff --git a/lib/shared/widgets/app_top_nav.dart b/lib/shared/widgets/app_top_nav.dart index 1a79ea5..b3feff6 100644 --- a/lib/shared/widgets/app_top_nav.dart +++ b/lib/shared/widgets/app_top_nav.dart @@ -8,6 +8,7 @@ import '../../core/config/dev_config.dart'; import '../../core/constants/app_constants.dart'; import '../../core/constants/route_constants.dart'; import '../../core/theme/app_colors.dart'; +import '../../core/theme/app_typography.dart'; import '../../core/theme/theme_provider.dart'; import '../../modules/settings/presentation/providers/settings_provider.dart'; import '../models/user_model.dart'; @@ -175,9 +176,9 @@ class _TopNavMenuLabel extends StatelessWidget { children: [ Text( label, - style: TextStyle( + style: AppTypography.label2( + weight: selected ? AppTypography.semiBold : AppTypography.medium, color: color, - fontWeight: selected ? FontWeight.w600 : FontWeight.w500, ), ), if (showChevron) ...[ diff --git a/lib/shared/widgets/entity_attachments_card.dart b/lib/shared/widgets/entity_attachments_card.dart index de6224b..5560df6 100644 --- a/lib/shared/widgets/entity_attachments_card.dart +++ b/lib/shared/widgets/entity_attachments_card.dart @@ -1,11 +1,10 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import '../../core/errors/failure.dart'; -import '../../core/network/api_handler.dart'; import '../../core/utils/formatters.dart'; import '../models/entity_attachment_model.dart'; import '../utils/file_download_helper.dart'; +import 'api_feedback.dart'; import 'app_confirmation_dialog.dart'; import 'app_toast.dart'; @@ -107,10 +106,9 @@ class _EntityAttachmentsCardState extends State { ); } catch (e) { if (!mounted) return; - final message = - e is Failure ? validationErrorMessage(e) : e.toString(); - showAppToastFromSnackBar(context, - SnackBar(content: Text(message)), + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), ); } finally { if (mounted) setState(() => _isUploading = false); @@ -128,8 +126,9 @@ class _EntityAttachmentsCardState extends State { ); } catch (e) { if (!mounted) return; - showAppToastFromSnackBar(context, - SnackBar(content: Text(e.toString())), + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), ); } finally { if (mounted) setState(() => _busyAttachmentId = null); @@ -158,10 +157,9 @@ class _EntityAttachmentsCardState extends State { ); } catch (e) { if (!mounted) return; - final message = - e is Failure ? validationErrorMessage(e) : e.toString(); - showAppToastFromSnackBar(context, - SnackBar(content: Text(message)), + showAppToastFromSnackBar( + context, + SnackBar(content: Text(errorDisplayMessage(e))), ); } finally { if (mounted) setState(() => _busyAttachmentId = null); diff --git a/lib/shared/widgets/master_inline_quick_add_form.dart b/lib/shared/widgets/master_inline_quick_add_form.dart index 8474ad0..60b0106 100644 --- a/lib/shared/widgets/master_inline_quick_add_form.dart +++ b/lib/shared/widgets/master_inline_quick_add_form.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/utils/validators.dart'; +import '../../core/theme/app_typography.dart'; import '../../modules/assets/data/repositories/asset_repository_impl.dart'; import '../../modules/master_data/data/repositories/master_repository_impl.dart'; import '../../modules/master_data/domain/entities/master_definition.dart'; @@ -241,7 +242,7 @@ class _MasterInlineQuickAddFormState return CheckboxListTile( contentPadding: EdgeInsets.zero, dense: true, - title: Text(field.label, style: const TextStyle(fontSize: 13)), + title: Text(field.label, style: AppTypography.body3()), value: _values[field.key] == true, onChanged: _submitting ? null diff --git a/lib/shared/widgets/user_avatar.dart b/lib/shared/widgets/user_avatar.dart index 1e50489..616d27e 100644 --- a/lib/shared/widgets/user_avatar.dart +++ b/lib/shared/widgets/user_avatar.dart @@ -1,6 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import '../../core/theme/app_typography.dart'; import '../../core/utils/media_url.dart'; /// Circular user avatar with network image fallback to name initial. @@ -24,9 +25,10 @@ class UserAvatar extends StatelessWidget { final fallback = Text( initial, - style: theme.textTheme.titleSmall?.copyWith( + style: TextStyle( + fontFamily: AppTypography.fontFamily, color: theme.colorScheme.primary, - fontWeight: FontWeight.w700, + fontWeight: AppTypography.bold, fontSize: radius * 0.85, ), ); diff --git a/web/.htaccess b/web/.htaccess index 8f0842b..9dbc5f3 100644 --- a/web/.htaccess +++ b/web/.htaccess @@ -1,9 +1,9 @@ -# SPA fallback for Flutter web path URL strategy (dev: /erp/). +# SPA fallback for Flutter web path URL strategy (root host). RewriteEngine On - RewriteBase /erp/ + RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule . /erp/index.html [L] + RewriteRule . /index.html [L] diff --git a/web/index.html b/web/index.html index 1632ad0..e019f3f 100644 --- a/web/index.html +++ b/web/index.html @@ -17,7 +17,7 @@