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,11 +83,17 @@ class _BranchesScreenState extends State<BranchesScreen> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
final errStr = e.toString().toLowerCase();
|
||||||
const SnackBar(
|
final isSessionExpired = e.runtimeType.toString().contains('SessionExpired') ||
|
||||||
content: Text('Error loading branches. Check connection.'),
|
errStr.contains('session expired') ||
|
||||||
),
|
errStr.contains('session_expired');
|
||||||
);
|
if (!isSessionExpired) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Error loading branches. Check connection.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,9 +52,15 @@ class _LoyaltyDashboardState extends State<LoyaltyDashboard> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
ScaffoldMessenger.of(
|
final errStr = e.toString().toLowerCase();
|
||||||
context,
|
final isSessionExpired = e.runtimeType.toString().contains('SessionExpired') ||
|
||||||
).showSnackBar(SnackBar(content: Text('Error loading data: $e')));
|
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,9 +55,15 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
final errStr = e.toString().toLowerCase();
|
||||||
SnackBar(content: Text('Error loading notifications: $e')),
|
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 notifications. Please try again.')),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,9 +33,15 @@ class _OrdersScreenState extends State<OrdersScreen> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
ScaffoldMessenger.of(
|
final errStr = e.toString().toLowerCase();
|
||||||
context,
|
final isSessionExpired = e.runtimeType.toString().contains('SessionExpired') ||
|
||||||
).showSnackBar(SnackBar(content: Text('Error loading history: $e')));
|
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?) ?? '',
|
'brand_logo': (res['brand_logo'] as String?) ?? '',
|
||||||
'primary_color': (res['primary_color'] as String?) ?? '#C62828',
|
'primary_color': (res['primary_color'] as String?) ?? '#C62828',
|
||||||
'secondary_color': (res['secondary_color'] as String?) ?? '#FF8F00',
|
'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_color': (res['background_color'] as String?) ?? '#FAF6EE',
|
||||||
'background_gradient_color': (res['background_gradient_color'] as String?) ?? '#F3EAD3',
|
'background_gradient_color': (res['background_gradient_color'] as String?) ?? '#F3EAD3',
|
||||||
};
|
};
|
||||||
@ -161,6 +162,7 @@ class OdooService {
|
|||||||
await ThemeManager.instance.updateConfig(
|
await ThemeManager.instance.updateConfig(
|
||||||
primaryHex: configMap['primary_color']!,
|
primaryHex: configMap['primary_color']!,
|
||||||
secondaryHex: configMap['secondary_color']!,
|
secondaryHex: configMap['secondary_color']!,
|
||||||
|
tertiaryHex: configMap['tertiary_color']!,
|
||||||
backgroundHex: configMap['background_color']!,
|
backgroundHex: configMap['background_color']!,
|
||||||
backgroundGradientHex: configMap['background_gradient_color']!,
|
backgroundGradientHex: configMap['background_gradient_color']!,
|
||||||
brandLogoB64: configMap['brand_logo']!,
|
brandLogoB64: configMap['brand_logo']!,
|
||||||
|
|||||||
@ -11,12 +11,14 @@ class ThemeManager extends ChangeNotifier {
|
|||||||
|
|
||||||
Color _primaryColor = AppTheme.primary;
|
Color _primaryColor = AppTheme.primary;
|
||||||
Color _secondaryColor = AppTheme.secondary;
|
Color _secondaryColor = AppTheme.secondary;
|
||||||
|
Color _tertiaryColor = AppTheme.tertiary;
|
||||||
Color _backgroundColor = AppTheme.surface;
|
Color _backgroundColor = AppTheme.surface;
|
||||||
Color _backgroundGradientColor = const Color(0xFFF3EAD3);
|
Color _backgroundGradientColor = const Color(0xFFF3EAD3);
|
||||||
String _brandLogo = '';
|
String _brandLogo = '';
|
||||||
|
|
||||||
Color get primaryColor => _primaryColor;
|
Color get primaryColor => _primaryColor;
|
||||||
Color get secondaryColor => _secondaryColor;
|
Color get secondaryColor => _secondaryColor;
|
||||||
|
Color get tertiaryColor => _tertiaryColor;
|
||||||
Color get backgroundColor => _backgroundColor;
|
Color get backgroundColor => _backgroundColor;
|
||||||
Color get backgroundGradientColor => _backgroundGradientColor;
|
Color get backgroundGradientColor => _backgroundGradientColor;
|
||||||
String get brandLogo => _brandLogo;
|
String get brandLogo => _brandLogo;
|
||||||
@ -24,6 +26,7 @@ class ThemeManager extends ChangeNotifier {
|
|||||||
ThemeData get themeData => AppTheme.getTheme(
|
ThemeData get themeData => AppTheme.getTheme(
|
||||||
primaryColor: _primaryColor,
|
primaryColor: _primaryColor,
|
||||||
secondaryColor: _secondaryColor,
|
secondaryColor: _secondaryColor,
|
||||||
|
tertiaryColor: _tertiaryColor,
|
||||||
backgroundColor: _backgroundColor,
|
backgroundColor: _backgroundColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -32,6 +35,7 @@ class ThemeManager extends ChangeNotifier {
|
|||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
final primHex = prefs.getString('theme_primary_color');
|
final primHex = prefs.getString('theme_primary_color');
|
||||||
final secHex = prefs.getString('theme_secondary_color');
|
final secHex = prefs.getString('theme_secondary_color');
|
||||||
|
final terHex = prefs.getString('theme_tertiary_color');
|
||||||
final bgHex = prefs.getString('theme_background_color');
|
final bgHex = prefs.getString('theme_background_color');
|
||||||
final bgGradHex = prefs.getString('theme_background_gradient_color');
|
final bgGradHex = prefs.getString('theme_background_gradient_color');
|
||||||
_brandLogo = prefs.getString('theme_brand_logo') ?? '';
|
_brandLogo = prefs.getString('theme_brand_logo') ?? '';
|
||||||
@ -42,6 +46,9 @@ class ThemeManager extends ChangeNotifier {
|
|||||||
if (secHex != null) {
|
if (secHex != null) {
|
||||||
_secondaryColor = _parseHexColor(secHex) ?? AppTheme.secondary;
|
_secondaryColor = _parseHexColor(secHex) ?? AppTheme.secondary;
|
||||||
}
|
}
|
||||||
|
if (terHex != null) {
|
||||||
|
_tertiaryColor = _parseHexColor(terHex) ?? AppTheme.tertiary;
|
||||||
|
}
|
||||||
if (bgHex != null) {
|
if (bgHex != null) {
|
||||||
_backgroundColor = _parseHexColor(bgHex) ?? AppTheme.surface;
|
_backgroundColor = _parseHexColor(bgHex) ?? AppTheme.surface;
|
||||||
}
|
}
|
||||||
@ -54,6 +61,7 @@ class ThemeManager extends ChangeNotifier {
|
|||||||
Future<void> updateConfig({
|
Future<void> updateConfig({
|
||||||
required String primaryHex,
|
required String primaryHex,
|
||||||
required String secondaryHex,
|
required String secondaryHex,
|
||||||
|
required String tertiaryHex,
|
||||||
required String backgroundHex,
|
required String backgroundHex,
|
||||||
required String backgroundGradientHex,
|
required String backgroundGradientHex,
|
||||||
required String brandLogoB64,
|
required String brandLogoB64,
|
||||||
@ -61,12 +69,14 @@ class ThemeManager extends ChangeNotifier {
|
|||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.setString('theme_primary_color', primaryHex);
|
await prefs.setString('theme_primary_color', primaryHex);
|
||||||
await prefs.setString('theme_secondary_color', secondaryHex);
|
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_color', backgroundHex);
|
||||||
await prefs.setString('theme_background_gradient_color', backgroundGradientHex);
|
await prefs.setString('theme_background_gradient_color', backgroundGradientHex);
|
||||||
await prefs.setString('theme_brand_logo', brandLogoB64);
|
await prefs.setString('theme_brand_logo', brandLogoB64);
|
||||||
|
|
||||||
_primaryColor = _parseHexColor(primaryHex) ?? AppTheme.primary;
|
_primaryColor = _parseHexColor(primaryHex) ?? AppTheme.primary;
|
||||||
_secondaryColor = _parseHexColor(secondaryHex) ?? AppTheme.secondary;
|
_secondaryColor = _parseHexColor(secondaryHex) ?? AppTheme.secondary;
|
||||||
|
_tertiaryColor = _parseHexColor(tertiaryHex) ?? AppTheme.tertiary;
|
||||||
_backgroundColor = _parseHexColor(backgroundHex) ?? AppTheme.surface;
|
_backgroundColor = _parseHexColor(backgroundHex) ?? AppTheme.surface;
|
||||||
_backgroundGradientColor = _parseHexColor(backgroundGradientHex) ?? const Color(0xFFF3EAD3);
|
_backgroundGradientColor = _parseHexColor(backgroundGradientHex) ?? const Color(0xFFF3EAD3);
|
||||||
_brandLogo = brandLogoB64;
|
_brandLogo = brandLogoB64;
|
||||||
|
|||||||
@ -7,8 +7,12 @@ class AppTheme {
|
|||||||
static const Color primaryContainer = Color(0xFF8A1C14);
|
static const Color primaryContainer = Color(0xFF8A1C14);
|
||||||
static const Color secondary = Color(0xFFB58428); // Warm Gold/Honey Amber
|
static const Color secondary = Color(0xFFB58428); // Warm Gold/Honey Amber
|
||||||
static const Color secondaryContainer = Color(0xFFF3DCA2);
|
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 onPrimaryContainer = Colors.white;
|
||||||
static const Color onSecondaryContainer = Color(0xFF5A3E00);
|
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
|
// Warm Ivory & Earthy Surface Hierarchy
|
||||||
static const Color surface = Color(0xFFFAF6EE); // Warm paper ivory background
|
static const Color surface = Color(0xFFFAF6EE); // Warm paper ivory background
|
||||||
@ -25,15 +29,28 @@ class AppTheme {
|
|||||||
|
|
||||||
static ThemeData get lightTheme => getTheme();
|
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 baseTheme = ThemeData.light();
|
||||||
final pColor = primaryColor ?? primary;
|
final pColor = primaryColor ?? primary;
|
||||||
final sColor = secondaryColor ?? secondary;
|
final sColor = secondaryColor ?? secondary;
|
||||||
|
final tColor = tertiaryColor ?? tertiary;
|
||||||
final bg = backgroundColor ?? surface;
|
final bg = backgroundColor ?? surface;
|
||||||
|
|
||||||
// Dynamically compute readable contrast text colors
|
// Dynamically compute readable contrast text colors
|
||||||
final onPrimaryColor = pColor.computeLuminance() > 0.5 ? Color(0xFF2E251B) : Colors.white;
|
final onPrimaryColor = pColor.computeLuminance() > 0.5 ? const Color(0xFF2E251B) : Colors.white;
|
||||||
final onSecondaryColor = sColor.computeLuminance() > 0.5 ? 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(
|
return ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
@ -41,13 +58,22 @@ class AppTheme {
|
|||||||
colorScheme: ColorScheme.light(
|
colorScheme: ColorScheme.light(
|
||||||
primary: pColor,
|
primary: pColor,
|
||||||
primaryContainer: pColor,
|
primaryContainer: pColor,
|
||||||
|
onPrimary: onPrimaryColor,
|
||||||
secondary: sColor,
|
secondary: sColor,
|
||||||
secondaryContainer: sColor.withValues(alpha: 0.15),
|
secondaryContainer: sColor.withValues(alpha: 0.15),
|
||||||
|
onSecondary: onSecondaryColor,
|
||||||
onSecondaryContainer: onSecondaryColor,
|
onSecondaryContainer: onSecondaryColor,
|
||||||
|
tertiary: tColor,
|
||||||
|
tertiaryContainer: tContainerColor,
|
||||||
|
onTertiary: onTertiaryColor,
|
||||||
|
onTertiaryContainer: onTertiaryContainerColor,
|
||||||
surface: bg,
|
surface: bg,
|
||||||
|
surfaceContainerLowest: lowestColor,
|
||||||
|
surfaceContainerLow: lowColor,
|
||||||
|
surfaceContainer: containerColor,
|
||||||
|
surfaceContainerHigh: highColor,
|
||||||
onSurface: onSurface,
|
onSurface: onSurface,
|
||||||
onSurfaceVariant: onSurfaceVariant,
|
onSurfaceVariant: onSurfaceVariant,
|
||||||
onPrimary: onPrimaryColor,
|
|
||||||
error: const Color(0xFFB02500),
|
error: const Color(0xFFB02500),
|
||||||
),
|
),
|
||||||
textTheme: baseTheme.textTheme.copyWith(
|
textTheme: baseTheme.textTheme.copyWith(
|
||||||
|
|||||||
@ -100,13 +100,18 @@ class _SubscriptionCard extends StatelessWidget {
|
|||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||||
child: PhysicalShape(
|
child: CustomPaint(
|
||||||
clipper: TicketClipper(),
|
foregroundPainter: TicketBorderPainter(
|
||||||
color: colorScheme.surfaceContainerLowest,
|
color: colorScheme.outline.withValues(alpha: 0.15),
|
||||||
elevation: 3,
|
strokeWidth: 1.0,
|
||||||
shadowColor: Colors.black.withValues(alpha: 0.15),
|
),
|
||||||
child: Container(
|
child: PhysicalShape(
|
||||||
padding: const EdgeInsets.all(20),
|
clipper: TicketClipper(),
|
||||||
|
color: colorScheme.surfaceContainerLow,
|
||||||
|
elevation: 2,
|
||||||
|
shadowColor: Colors.black.withValues(alpha: 0.08),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -282,8 +287,9 @@ class _SubscriptionCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Custom Clipper for a ticket-like appearance with side notches.
|
/// Custom Clipper for a ticket-like appearance with side notches.
|
||||||
@ -345,3 +351,47 @@ class DashedLinePainter extends CustomPainter {
|
|||||||
@override
|
@override
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
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