refactor: suppress error snackbars on session expiration, add tertiary theme color, and update subscription ticket UI styling.
This commit is contained in:
parent
4df528272e
commit
f1a08d6396
@ -83,6 +83,11 @@ class _BranchesScreenState extends State<BranchesScreen> {
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
final errStr = e.toString().toLowerCase();
|
||||
final isSessionExpired = e.runtimeType.toString().contains('SessionExpired') ||
|
||||
errStr.contains('session expired') ||
|
||||
errStr.contains('session_expired');
|
||||
if (!isSessionExpired) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Error loading branches. Check connection.'),
|
||||
@ -91,6 +96,7 @@ class _BranchesScreenState extends State<BranchesScreen> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Haversine formula — returns distance in kilometres.
|
||||
double _distanceTo(Position pos, dynamic branch) {
|
||||
|
||||
@ -52,9 +52,15 @@ class _LoyaltyDashboardState extends State<LoyaltyDashboard> {
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error loading data: $e')));
|
||||
final errStr = e.toString().toLowerCase();
|
||||
final isSessionExpired = e.runtimeType.toString().contains('SessionExpired') ||
|
||||
errStr.contains('session expired') ||
|
||||
errStr.contains('session_expired');
|
||||
if (!isSessionExpired) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Failed to load dashboard. Please try again.')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,12 +55,18 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
final errStr = e.toString().toLowerCase();
|
||||
final isSessionExpired = e.runtimeType.toString().contains('SessionExpired') ||
|
||||
errStr.contains('session expired') ||
|
||||
errStr.contains('session_expired');
|
||||
if (!isSessionExpired) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Error loading notifications: $e')),
|
||||
const SnackBar(content: Text('Failed to load notifications. Please try again.')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@ -33,9 +33,15 @@ class _OrdersScreenState extends State<OrdersScreen> {
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Error loading history: $e')));
|
||||
final errStr = e.toString().toLowerCase();
|
||||
final isSessionExpired = e.runtimeType.toString().contains('SessionExpired') ||
|
||||
errStr.contains('session expired') ||
|
||||
errStr.contains('session_expired');
|
||||
if (!isSessionExpired) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Failed to load order history. Please try again.')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,6 +154,7 @@ class OdooService {
|
||||
'brand_logo': (res['brand_logo'] as String?) ?? '',
|
||||
'primary_color': (res['primary_color'] as String?) ?? '#C62828',
|
||||
'secondary_color': (res['secondary_color'] as String?) ?? '#FF8F00',
|
||||
'tertiary_color': (res['tertiary_color'] as String?) ?? '#4A7C59',
|
||||
'background_color': (res['background_color'] as String?) ?? '#FAF6EE',
|
||||
'background_gradient_color': (res['background_gradient_color'] as String?) ?? '#F3EAD3',
|
||||
};
|
||||
@ -161,6 +162,7 @@ class OdooService {
|
||||
await ThemeManager.instance.updateConfig(
|
||||
primaryHex: configMap['primary_color']!,
|
||||
secondaryHex: configMap['secondary_color']!,
|
||||
tertiaryHex: configMap['tertiary_color']!,
|
||||
backgroundHex: configMap['background_color']!,
|
||||
backgroundGradientHex: configMap['background_gradient_color']!,
|
||||
brandLogoB64: configMap['brand_logo']!,
|
||||
|
||||
@ -11,12 +11,14 @@ class ThemeManager extends ChangeNotifier {
|
||||
|
||||
Color _primaryColor = AppTheme.primary;
|
||||
Color _secondaryColor = AppTheme.secondary;
|
||||
Color _tertiaryColor = AppTheme.tertiary;
|
||||
Color _backgroundColor = AppTheme.surface;
|
||||
Color _backgroundGradientColor = const Color(0xFFF3EAD3);
|
||||
String _brandLogo = '';
|
||||
|
||||
Color get primaryColor => _primaryColor;
|
||||
Color get secondaryColor => _secondaryColor;
|
||||
Color get tertiaryColor => _tertiaryColor;
|
||||
Color get backgroundColor => _backgroundColor;
|
||||
Color get backgroundGradientColor => _backgroundGradientColor;
|
||||
String get brandLogo => _brandLogo;
|
||||
@ -24,6 +26,7 @@ class ThemeManager extends ChangeNotifier {
|
||||
ThemeData get themeData => AppTheme.getTheme(
|
||||
primaryColor: _primaryColor,
|
||||
secondaryColor: _secondaryColor,
|
||||
tertiaryColor: _tertiaryColor,
|
||||
backgroundColor: _backgroundColor,
|
||||
);
|
||||
|
||||
@ -32,6 +35,7 @@ class ThemeManager extends ChangeNotifier {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final primHex = prefs.getString('theme_primary_color');
|
||||
final secHex = prefs.getString('theme_secondary_color');
|
||||
final terHex = prefs.getString('theme_tertiary_color');
|
||||
final bgHex = prefs.getString('theme_background_color');
|
||||
final bgGradHex = prefs.getString('theme_background_gradient_color');
|
||||
_brandLogo = prefs.getString('theme_brand_logo') ?? '';
|
||||
@ -42,6 +46,9 @@ class ThemeManager extends ChangeNotifier {
|
||||
if (secHex != null) {
|
||||
_secondaryColor = _parseHexColor(secHex) ?? AppTheme.secondary;
|
||||
}
|
||||
if (terHex != null) {
|
||||
_tertiaryColor = _parseHexColor(terHex) ?? AppTheme.tertiary;
|
||||
}
|
||||
if (bgHex != null) {
|
||||
_backgroundColor = _parseHexColor(bgHex) ?? AppTheme.surface;
|
||||
}
|
||||
@ -54,6 +61,7 @@ class ThemeManager extends ChangeNotifier {
|
||||
Future<void> updateConfig({
|
||||
required String primaryHex,
|
||||
required String secondaryHex,
|
||||
required String tertiaryHex,
|
||||
required String backgroundHex,
|
||||
required String backgroundGradientHex,
|
||||
required String brandLogoB64,
|
||||
@ -61,12 +69,14 @@ class ThemeManager extends ChangeNotifier {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('theme_primary_color', primaryHex);
|
||||
await prefs.setString('theme_secondary_color', secondaryHex);
|
||||
await prefs.setString('theme_tertiary_color', tertiaryHex);
|
||||
await prefs.setString('theme_background_color', backgroundHex);
|
||||
await prefs.setString('theme_background_gradient_color', backgroundGradientHex);
|
||||
await prefs.setString('theme_brand_logo', brandLogoB64);
|
||||
|
||||
_primaryColor = _parseHexColor(primaryHex) ?? AppTheme.primary;
|
||||
_secondaryColor = _parseHexColor(secondaryHex) ?? AppTheme.secondary;
|
||||
_tertiaryColor = _parseHexColor(tertiaryHex) ?? AppTheme.tertiary;
|
||||
_backgroundColor = _parseHexColor(backgroundHex) ?? AppTheme.surface;
|
||||
_backgroundGradientColor = _parseHexColor(backgroundGradientHex) ?? const Color(0xFFF3EAD3);
|
||||
_brandLogo = brandLogoB64;
|
||||
|
||||
@ -7,8 +7,12 @@ class AppTheme {
|
||||
static const Color primaryContainer = Color(0xFF8A1C14);
|
||||
static const Color secondary = Color(0xFFB58428); // Warm Gold/Honey Amber
|
||||
static const Color secondaryContainer = Color(0xFFF3DCA2);
|
||||
static const Color tertiary = Color(0xFF4A7C59); // Warm Sage/Forest Green
|
||||
static const Color tertiaryContainer = Color(0xFFCCE8D6); // Soft sage tint
|
||||
static const Color onPrimaryContainer = Colors.white;
|
||||
static const Color onSecondaryContainer = Color(0xFF5A3E00);
|
||||
static const Color onTertiary = Colors.white;
|
||||
static const Color onTertiaryContainer = Color(0xFF0D3320); // Deep forest text
|
||||
|
||||
// Warm Ivory & Earthy Surface Hierarchy
|
||||
static const Color surface = Color(0xFFFAF6EE); // Warm paper ivory background
|
||||
@ -25,15 +29,28 @@ class AppTheme {
|
||||
|
||||
static ThemeData get lightTheme => getTheme();
|
||||
|
||||
static ThemeData getTheme({Color? primaryColor, Color? secondaryColor, Color? backgroundColor}) {
|
||||
static ThemeData getTheme({Color? primaryColor, Color? secondaryColor, Color? tertiaryColor, Color? backgroundColor}) {
|
||||
final baseTheme = ThemeData.light();
|
||||
final pColor = primaryColor ?? primary;
|
||||
final sColor = secondaryColor ?? secondary;
|
||||
final tColor = tertiaryColor ?? tertiary;
|
||||
final bg = backgroundColor ?? surface;
|
||||
|
||||
// Dynamically compute readable contrast text colors
|
||||
final onPrimaryColor = pColor.computeLuminance() > 0.5 ? Color(0xFF2E251B) : Colors.white;
|
||||
final onSecondaryColor = sColor.computeLuminance() > 0.5 ? Color(0xFF2E251B) : Colors.white;
|
||||
final onPrimaryColor = pColor.computeLuminance() > 0.5 ? const Color(0xFF2E251B) : Colors.white;
|
||||
final onSecondaryColor = sColor.computeLuminance() > 0.5 ? const Color(0xFF2E251B) : Colors.white;
|
||||
final onTertiaryColor = tColor.computeLuminance() > 0.5 ? const Color(0xFF2E251B) : Colors.white;
|
||||
// Tertiary container: a desaturated/lightened tint of the tertiary color
|
||||
final tContainerColor = Color.lerp(tColor, Colors.white, 0.75) ?? tertiaryContainer;
|
||||
final onTertiaryContainerColor = tColor.computeLuminance() > 0.5
|
||||
? const Color(0xFF2E251B)
|
||||
: Color.lerp(tColor, Colors.black, 0.7) ?? onTertiaryContainer;
|
||||
|
||||
final isWhiteBg = bg.r > 0.98 && bg.g > 0.98 && bg.b > 0.98;
|
||||
final lowestColor = isWhiteBg ? const Color(0xFFFCFAF7) : const Color(0xFFFCFAF6);
|
||||
final lowColor = isWhiteBg ? const Color(0xFFF5F3ED) : const Color(0xFFF7F1E3);
|
||||
final containerColor = isWhiteBg ? const Color(0xFFEDEAE1) : const Color(0xFFF2EAD8);
|
||||
final highColor = isWhiteBg ? const Color(0xFFE2DDD2) : const Color(0xFFE5D5BA);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
@ -41,13 +58,22 @@ class AppTheme {
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: pColor,
|
||||
primaryContainer: pColor,
|
||||
onPrimary: onPrimaryColor,
|
||||
secondary: sColor,
|
||||
secondaryContainer: sColor.withValues(alpha: 0.15),
|
||||
onSecondary: onSecondaryColor,
|
||||
onSecondaryContainer: onSecondaryColor,
|
||||
tertiary: tColor,
|
||||
tertiaryContainer: tContainerColor,
|
||||
onTertiary: onTertiaryColor,
|
||||
onTertiaryContainer: onTertiaryContainerColor,
|
||||
surface: bg,
|
||||
surfaceContainerLowest: lowestColor,
|
||||
surfaceContainerLow: lowColor,
|
||||
surfaceContainer: containerColor,
|
||||
surfaceContainerHigh: highColor,
|
||||
onSurface: onSurface,
|
||||
onSurfaceVariant: onSurfaceVariant,
|
||||
onPrimary: onPrimaryColor,
|
||||
error: const Color(0xFFB02500),
|
||||
),
|
||||
textTheme: baseTheme.textTheme.copyWith(
|
||||
|
||||
@ -100,11 +100,16 @@ class _SubscriptionCard extends StatelessWidget {
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
child: CustomPaint(
|
||||
foregroundPainter: TicketBorderPainter(
|
||||
color: colorScheme.outline.withValues(alpha: 0.15),
|
||||
strokeWidth: 1.0,
|
||||
),
|
||||
child: PhysicalShape(
|
||||
clipper: TicketClipper(),
|
||||
color: colorScheme.surfaceContainerLowest,
|
||||
elevation: 3,
|
||||
shadowColor: Colors.black.withValues(alpha: 0.15),
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withValues(alpha: 0.08),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
@ -282,8 +287,9 @@ class _SubscriptionCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom Clipper for a ticket-like appearance with side notches.
|
||||
@ -345,3 +351,47 @@ class DashedLinePainter extends CustomPainter {
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
/// Painter to draw the outer outline of the ticket shape.
|
||||
class TicketBorderPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final double strokeWidth;
|
||||
|
||||
TicketBorderPainter({required this.color, this.strokeWidth = 1.0});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = strokeWidth;
|
||||
|
||||
final path = Path();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(size.width, 0);
|
||||
|
||||
const double notchY = 49.0;
|
||||
const double notchRadius = 8.0;
|
||||
path.lineTo(size.width, notchY - notchRadius);
|
||||
path.arcToPoint(
|
||||
Offset(size.width, notchY + notchRadius),
|
||||
radius: const Radius.circular(notchRadius),
|
||||
clockwise: false,
|
||||
);
|
||||
path.lineTo(size.width, size.height);
|
||||
path.lineTo(0, size.height);
|
||||
|
||||
path.lineTo(0, notchY + notchRadius);
|
||||
path.arcToPoint(
|
||||
const Offset(0, notchY - notchRadius),
|
||||
radius: const Radius.circular(notchRadius),
|
||||
clockwise: false,
|
||||
);
|
||||
path.close();
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user