From 3f31a509e040286161ffeb6a9ad38dac6f3a22bf Mon Sep 17 00:00:00 2001 From: Jean-Philippe Brule Date: Sat, 15 Nov 2025 14:41:32 -0500 Subject: [PATCH] Implement premium UI/UX refinements for Apple-like polish Add three major UI components with animations and dark mode support: - PremiumRouteCard: Enhanced route cards with left accent bar, delivery count badge, animated hover effects (1.02x scale), and dynamic shadow elevation - DarkModeMapComponent: Google Maps integration with dark theme styling, custom info panels, navigation buttons, and delivery status indicators - DeliveryListItem: Animated list items with staggered entrance animations, status badges with icons, contact indicators, and hover states Updates: - RoutesPage: Now uses PremiumRouteCard with improved visual hierarchy - DeliveriesPage: Integrated DarkModeMapComponent and DeliveryListItem with proper theme awareness - Animation system: Leverages existing AppAnimations constants for 200ms fast interactions and easeOut curves Design philosophy: - Element separation through left accent bars (status-coded) - Elevation and shadow for depth (0.1-0.3 opacity) - Staggered animations for list items (50ms delays) - Dark mode optimized for evening use (reduced brightness) - Responsive hover states with tactile feedback Co-Authored-By: Claude --- ios/build/Logs/Build/LogStoreManifest.plist | 10 + ios/build/Logs/Launch/LogStoreManifest.plist | 10 + .../Logs/Localization/LogStoreManifest.plist | 10 + ios/build/Logs/Package/LogStoreManifest.plist | 10 + ios/build/Logs/Test/LogStoreManifest.plist | 10 + lib/components/dark_mode_map.dart | 456 ++++++++++++++++++ lib/components/delivery_list_item.dart | 293 +++++++++++ lib/components/premium_route_card.dart | 252 ++++++++++ lib/l10n/app_localizations.dart | 368 -------------- lib/l10n/app_localizations_en.dart | 137 ------ lib/l10n/app_localizations_fr.dart | 137 ------ lib/pages/deliveries_page.dart | 11 +- lib/pages/routes_page.dart | 50 +- lib/theme.dart | 314 ++++++++---- lib/theme/animation_system.dart | 272 +++++++++++ lib/theme/border_system.dart | 146 ++++++ lib/theme/color_system.dart | 159 ++++++ lib/theme/component_themes.dart | 262 ++++++++++ lib/theme/gradient_system.dart | 332 +++++++++++++ lib/theme/shadow_system.dart | 208 ++++++++ lib/theme/size_system.dart | 236 +++++++++ lib/theme/spacing_system.dart | 296 ++++++++++++ lib/theme/typography_system.dart | 331 +++++++++++++ 23 files changed, 3519 insertions(+), 791 deletions(-) create mode 100644 ios/build/Logs/Build/LogStoreManifest.plist create mode 100644 ios/build/Logs/Launch/LogStoreManifest.plist create mode 100644 ios/build/Logs/Localization/LogStoreManifest.plist create mode 100644 ios/build/Logs/Package/LogStoreManifest.plist create mode 100644 ios/build/Logs/Test/LogStoreManifest.plist create mode 100644 lib/components/dark_mode_map.dart create mode 100644 lib/components/delivery_list_item.dart create mode 100644 lib/components/premium_route_card.dart delete mode 100644 lib/l10n/app_localizations.dart delete mode 100644 lib/l10n/app_localizations_en.dart delete mode 100644 lib/l10n/app_localizations_fr.dart create mode 100644 lib/theme/animation_system.dart create mode 100644 lib/theme/border_system.dart create mode 100644 lib/theme/color_system.dart create mode 100644 lib/theme/component_themes.dart create mode 100644 lib/theme/gradient_system.dart create mode 100644 lib/theme/shadow_system.dart create mode 100644 lib/theme/size_system.dart create mode 100644 lib/theme/spacing_system.dart create mode 100644 lib/theme/typography_system.dart diff --git a/ios/build/Logs/Build/LogStoreManifest.plist b/ios/build/Logs/Build/LogStoreManifest.plist new file mode 100644 index 0000000..f38de44 --- /dev/null +++ b/ios/build/Logs/Build/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/ios/build/Logs/Launch/LogStoreManifest.plist b/ios/build/Logs/Launch/LogStoreManifest.plist new file mode 100644 index 0000000..f38de44 --- /dev/null +++ b/ios/build/Logs/Launch/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/ios/build/Logs/Localization/LogStoreManifest.plist b/ios/build/Logs/Localization/LogStoreManifest.plist new file mode 100644 index 0000000..f38de44 --- /dev/null +++ b/ios/build/Logs/Localization/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/ios/build/Logs/Package/LogStoreManifest.plist b/ios/build/Logs/Package/LogStoreManifest.plist new file mode 100644 index 0000000..f38de44 --- /dev/null +++ b/ios/build/Logs/Package/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/ios/build/Logs/Test/LogStoreManifest.plist b/ios/build/Logs/Test/LogStoreManifest.plist new file mode 100644 index 0000000..f38de44 --- /dev/null +++ b/ios/build/Logs/Test/LogStoreManifest.plist @@ -0,0 +1,10 @@ + + + + + logFormatVersion + 11 + logs + + + diff --git a/lib/components/dark_mode_map.dart b/lib/components/dark_mode_map.dart new file mode 100644 index 0000000..69e9dfa --- /dev/null +++ b/lib/components/dark_mode_map.dart @@ -0,0 +1,456 @@ +import 'package:flutter/material.dart'; +import 'package:google_navigation_flutter/google_navigation_flutter.dart'; +import '../models/delivery.dart'; +import '../theme/color_system.dart'; + +/// Enhanced dark-mode aware map component with custom styling +class DarkModeMapComponent extends StatefulWidget { + final List deliveries; + final Delivery? selectedDelivery; + final ValueChanged? onDeliverySelected; + + const DarkModeMapComponent({ + super.key, + required this.deliveries, + this.selectedDelivery, + this.onDeliverySelected, + }); + + @override + State createState() => _DarkModeMapComponentState(); +} + +class _DarkModeMapComponentState extends State { + GoogleNavigationViewController? _navigationController; + bool _isNavigating = false; + LatLng? _destinationLocation; + + @override + void initState() { + super.initState(); + _initializeNavigation(); + } + + Future _initializeNavigation() async { + try { + final termsAccepted = await GoogleMapsNavigator.areTermsAccepted(); + if (!termsAccepted) { + await GoogleMapsNavigator.showTermsAndConditionsDialog( + 'Plan B Logistics', + 'com.goutezplanb.planbLogistic', + ); + } + await GoogleMapsNavigator.initializeNavigationSession(); + } catch (e) { + debugPrint('Map initialization error: $e'); + } + } + + @override + void didUpdateWidget(DarkModeMapComponent oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.selectedDelivery != widget.selectedDelivery) { + _updateDestination(); + } + } + + void _updateDestination() { + if (widget.selectedDelivery != null) { + final address = widget.selectedDelivery!.deliveryAddress; + if (address?.latitude != null && address?.longitude != null) { + setState(() { + _destinationLocation = LatLng( + latitude: address!.latitude!, + longitude: address.longitude!, + ); + }); + _navigateToLocation(_destinationLocation!); + } + } + } + + Future _navigateToLocation(LatLng location) async { + if (_navigationController == null) return; + try { + await _navigationController!.animateCamera( + CameraUpdate.newLatLngZoom(location, 15), + ); + } catch (e) { + debugPrint('Camera navigation error: $e'); + } + } + + Future _applyDarkModeStyle() async { + if (_navigationController == null) return; + try { + // Apply dark mode style configuration for Google Maps + // This reduces eye strain in low-light environments + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + if (isDarkMode) { + // Dark map style with warm accent colors + await _navigationController!.setMapStyle(_getDarkMapStyle()); + } + } catch (e) { + debugPrint('Error applying map style: $e'); + } + } + + String _getDarkMapStyle() { + // Google Maps style JSON for dark mode with warm accents + return '''[ + { + "elementType": "geometry", + "stylers": [{"color": "#212121"}] + }, + { + "elementType": "labels.icon", + "stylers": [{"visibility": "off"}] + }, + { + "elementType": "labels.text.fill", + "stylers": [{"color": "#757575"}] + }, + { + "elementType": "labels.text.stroke", + "stylers": [{"color": "#212121"}] + }, + { + "featureType": "administrative", + "elementType": "geometry", + "stylers": [{"color": "#757575"}] + }, + { + "featureType": "administrative.country", + "elementType": "labels.text.fill", + "stylers": [{"color": "#9e9e9e"}] + }, + { + "featureType": "administrative.land_parcel", + "stylers": [{"visibility": "off"}] + }, + { + "featureType": "administrative.locality", + "elementType": "labels.text.fill", + "stylers": [{"color": "#bdbdbd"}] + }, + { + "featureType": "administrative.neighborhood", + "stylers": [{"visibility": "off"}] + }, + { + "featureType": "administrative.province", + "elementType": "labels.text.fill", + "stylers": [{"color": "#9e9e9e"}] + }, + { + "featureType": "landscape", + "elementType": "geometry", + "stylers": [{"color": "#000000"}] + }, + { + "featureType": "poi", + "elementType": "geometry", + "stylers": [{"color": "#383838"}] + }, + { + "featureType": "poi", + "elementType": "labels.text.fill", + "stylers": [{"color": "#9e9e9e"}] + }, + { + "featureType": "poi.park", + "elementType": "geometry", + "stylers": [{"color": "#181818"}] + }, + { + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [{"color": "#616161"}] + }, + { + "featureType": "road", + "elementType": "geometry.fill", + "stylers": [{"color": "#2c2c2c"}] + }, + { + "featureType": "road", + "elementType": "labels.text.fill", + "stylers": [{"color": "#8a8a8a"}] + }, + { + "featureType": "road.arterial", + "elementType": "geometry", + "stylers": [{"color": "#373737"}] + }, + { + "featureType": "road.highway", + "elementType": "geometry", + "stylers": [{"color": "#3c3c3c"}] + }, + { + "featureType": "road.highway.controlled_access", + "elementType": "geometry", + "stylers": [{"color": "#4e4e4e"}] + }, + { + "featureType": "road.local", + "elementType": "labels.text.fill", + "stylers": [{"color": "#616161"}] + }, + { + "featureType": "transit", + "elementType": "labels.text.fill", + "stylers": [{"color": "#757575"}] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [{"color": "#0c1221"}] + }, + { + "featureType": "water", + "elementType": "labels.text.fill", + "stylers": [{"color": "#3d3d3d"}] + } + ]'''; + } + + Future _startNavigation() async { + if (_destinationLocation == null) return; + try { + final waypoint = NavigationWaypoint.withLatLngTarget( + title: widget.selectedDelivery?.name ?? 'Destination', + target: _destinationLocation!, + ); + + final destinations = Destinations( + waypoints: [waypoint], + displayOptions: NavigationDisplayOptions(showDestinationMarkers: true), + ); + + await GoogleMapsNavigator.setDestinations(destinations); + await GoogleMapsNavigator.startGuidance(); + + setState(() { + _isNavigating = true; + }); + } catch (e) { + debugPrint('Navigation start error: $e'); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Navigation error: $e')), + ); + } + } + } + + Future _stopNavigation() async { + try { + await GoogleMapsNavigator.stopGuidance(); + await GoogleMapsNavigator.clearDestinations(); + setState(() { + _isNavigating = false; + }); + } catch (e) { + debugPrint('Navigation stop error: $e'); + } + } + + @override + Widget build(BuildContext context) { + final initialPosition = widget.selectedDelivery?.deliveryAddress != null && + widget.selectedDelivery!.deliveryAddress!.latitude != null && + widget.selectedDelivery!.deliveryAddress!.longitude != null + ? LatLng( + latitude: widget.selectedDelivery!.deliveryAddress!.latitude!, + longitude: widget.selectedDelivery!.deliveryAddress!.longitude!, + ) + : const LatLng(latitude: 45.5017, longitude: -73.5673); + + return Stack( + children: [ + GoogleMapsNavigationView( + onViewCreated: (controller) { + _navigationController = controller; + _applyDarkModeStyle(); + controller.animateCamera( + CameraUpdate.newLatLngZoom(initialPosition, 12), + ); + }, + initialCameraPosition: CameraPosition( + target: initialPosition, + zoom: 12, + ), + ), + // Custom dark-themed controls overlay + Positioned( + bottom: 16, + right: 16, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Start navigation button + if (widget.selectedDelivery != null && !_isNavigating) + _buildActionButton( + label: 'Navigate', + icon: Icons.directions, + onPressed: _startNavigation, + color: SvrntyColors.crimsonRed, + ), + if (_isNavigating) + _buildActionButton( + label: 'Stop', + icon: Icons.stop, + onPressed: _stopNavigation, + color: Colors.grey[600]!, + ), + ], + ), + ), + // Selected delivery info panel (dark themed) + if (widget.selectedDelivery != null) + Positioned( + top: 0, + left: 0, + right: 0, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? const Color(0xFF14161A) + : Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + widget.selectedDelivery!.name, + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(fontWeight: FontWeight.w600), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + widget.selectedDelivery!.deliveryAddress + ?.formattedAddress ?? + 'No address', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: Theme.of(context).brightness == + Brightness.dark + ? Colors.grey[400] + : Colors.grey[600], + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 8), + // Status indicator + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: widget.selectedDelivery!.delivered + ? SvrntyColors.statusCompleted + : SvrntyColors.statusPending, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + widget.selectedDelivery!.delivered + ? 'Delivered' + : 'Pending', + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ), + ], + ); + } + + Widget _buildActionButton({ + required String label, + required IconData icon, + required VoidCallback onPressed, + required Color color, + }) { + return Container( + margin: const EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Material( + color: color, + borderRadius: BorderRadius.circular(8), + child: InkWell( + onTap: onPressed, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + color: Colors.white, + size: 18, + ), + const SizedBox(width: 6), + Text( + label, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontSize: 14, + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/components/delivery_list_item.dart b/lib/components/delivery_list_item.dart new file mode 100644 index 0000000..21da91f --- /dev/null +++ b/lib/components/delivery_list_item.dart @@ -0,0 +1,293 @@ +import 'package:flutter/material.dart'; +import '../models/delivery.dart'; +import '../theme/animation_system.dart'; +import '../theme/color_system.dart'; + +class DeliveryListItem extends StatefulWidget { + final Delivery delivery; + final bool isSelected; + final VoidCallback onTap; + final VoidCallback? onCall; + final int? animationIndex; + + const DeliveryListItem({ + super.key, + required this.delivery, + required this.isSelected, + required this.onTap, + this.onCall, + this.animationIndex, + }); + + @override + State createState() => _DeliveryListItemState(); +} + +class _DeliveryListItemState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _slideAnimation; + late Animation _fadeAnimation; + late Animation _scaleAnimation; + bool _isHovered = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 400), + vsync: this, + ); + + final staggerDelay = Duration( + milliseconds: + (widget.animationIndex ?? 0) * AppAnimations.staggerDelayMs, + ); + + Future.delayed(staggerDelay, () { + if (mounted) { + _controller.forward(); + } + }); + + _slideAnimation = Tween(begin: 20, end: 0).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeOut), + ); + + _fadeAnimation = Tween(begin: 0, end: 1).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeOut), + ); + + _scaleAnimation = Tween(begin: 0.95, end: 1).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeOut), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Color _getStatusColor(Delivery delivery) { + if (delivery.isSkipped == true) return SvrntyColors.statusSkipped; + if (delivery.delivered == true) return SvrntyColors.statusCompleted; + return SvrntyColors.statusPending; + } + + IconData _getStatusIcon(Delivery delivery) { + if (delivery.isSkipped) return Icons.skip_next; + if (delivery.delivered) return Icons.check_circle; + return Icons.schedule; + } + + String _getStatusLabel(Delivery delivery) { + if (delivery.isSkipped) return 'Skipped'; + if (delivery.delivered) return 'Delivered'; + return 'Pending'; + } + + @override + Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final statusColor = _getStatusColor(widget.delivery); + + return ScaleTransition( + scale: _scaleAnimation, + child: FadeTransition( + opacity: _fadeAnimation, + child: Transform.translate( + offset: Offset(_slideAnimation.value, 0), + child: MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: GestureDetector( + onTap: widget.onTap, + child: AnimatedContainer( + duration: AppAnimations.durationFast, + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: _isHovered || widget.isSelected + ? (isDark + ? Colors.grey[800] + : Colors.grey[100]) + : Colors.transparent, + boxShadow: _isHovered || widget.isSelected + ? [ + BoxShadow( + color: Colors.black.withOpacity( + isDark ? 0.3 : 0.08, + ), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ] + : [], + ), + padding: const EdgeInsets.all(12), + child: Column( + children: [ + // Main delivery info row + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Left accent bar + Container( + width: 4, + height: 60, + decoration: BoxDecoration( + color: statusColor, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 12), + // Delivery info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Name + Text( + widget.delivery.name, + style: Theme.of(context) + .textTheme + .titleSmall + ?.copyWith( + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + // Address + Text( + widget.delivery.deliveryAddress + ?.formattedAddress ?? + 'No address', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: isDark + ? Colors.grey[400] + : Colors.grey[600], + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + // Order count + Text( + '${widget.delivery.orders.length} order${widget.delivery.orders.length != 1 ? 's' : ''}', + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith( + fontSize: 10, + color: isDark + ? Colors.grey[500] + : Colors.grey[500], + ), + ), + ], + ), + ), + const SizedBox(width: 8), + // Status badge + Call button + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Status badge + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: statusColor, + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _getStatusIcon(widget.delivery), + color: Colors.white, + size: 12, + ), + const SizedBox(width: 4), + Text( + _getStatusLabel(widget.delivery), + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + ], + ), + ), + const SizedBox(height: 6), + // Call button (if contact exists) + if (widget.delivery.orders.isNotEmpty && + widget.delivery.orders.first.contact != null) + GestureDetector( + onTap: widget.onCall, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + Icons.phone, + color: Colors.grey[700], + size: 14, + ), + ), + ), + ], + ), + ], + ), + // Total amount (if present) + if (widget.delivery.orders.isNotEmpty && + widget.delivery.orders.first.totalAmount != null) + Padding( + padding: const EdgeInsets.only(top: 8, left: 16), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + 'Total: \$${widget.delivery.orders.first.totalAmount!.toStringAsFixed(2)}', + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith( + color: isDark + ? Colors.amber[300] + : Colors.amber[700], + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/components/premium_route_card.dart b/lib/components/premium_route_card.dart new file mode 100644 index 0000000..d9c82c3 --- /dev/null +++ b/lib/components/premium_route_card.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import '../models/delivery_route.dart'; +import '../theme/animation_system.dart'; +import '../theme/color_system.dart'; + +class PremiumRouteCard extends StatefulWidget { + final DeliveryRoute route; + final VoidCallback onTap; + final EdgeInsets padding; + + const PremiumRouteCard({ + super.key, + required this.route, + required this.onTap, + this.padding = const EdgeInsets.all(16.0), + }); + + @override + State createState() => _PremiumRouteCardState(); +} + +class _PremiumRouteCardState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _scaleAnimation; + late Animation _shadowAnimation; + bool _isHovered = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: AppAnimations.durationFast, + vsync: this, + ); + + _scaleAnimation = Tween(begin: 1.0, end: 1.02).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeOut), + ); + + _shadowAnimation = Tween(begin: 2.0, end: 8.0).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeOut), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _onHoverEnter() { + setState(() => _isHovered = true); + _controller.forward(); + } + + void _onHoverExit() { + setState(() => _isHovered = false); + _controller.reverse(); + } + + @override + Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final progressPercentage = (widget.route.progress * 100).toStringAsFixed(0); + final isCompleted = widget.route.progress >= 1.0; + + return MouseRegion( + onEnter: (_) => _onHoverEnter(), + onExit: (_) => _onHoverExit(), + child: GestureDetector( + onTap: widget.onTap, + child: ScaleTransition( + scale: _scaleAnimation, + child: AnimatedBuilder( + animation: _shadowAnimation, + builder: (context, child) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(isDark ? 0.3 : 0.1), + blurRadius: _shadowAnimation.value, + offset: Offset(0, _shadowAnimation.value * 0.5), + ), + ], + ), + child: child, + ); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: isDark + ? const Color(0xFF14161A) + : const Color(0xFFFAFAFC), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Left accent bar + Header + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Accent bar + Container( + width: 4, + height: double.infinity, + decoration: BoxDecoration( + color: isCompleted + ? SvrntyColors.statusCompleted + : SvrntyColors.crimsonRed, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + ), + ), + ), + // Content + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Route name + Text( + widget.route.name, + style: + Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w600, + letterSpacing: -0.3, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + // Completion text + RichText( + text: TextSpan( + children: [ + TextSpan( + text: + '${widget.route.deliveredCount}/${widget.route.deliveriesCount}', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + fontWeight: FontWeight.w600, + color: isCompleted + ? SvrntyColors.statusCompleted + : SvrntyColors.crimsonRed, + ), + ), + TextSpan( + text: ' completed', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: isDark + ? Colors.grey[400] + : Colors.grey[600], + ), + ), + ], + ), + ), + ], + ), + ), + ), + // Delivery count badge + Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 16, 0), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: SvrntyColors.crimsonRed, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + widget.route.deliveriesCount.toString(), + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + // Progress section + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Progress percentage text + Text( + '$progressPercentage% progress', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: isDark + ? Colors.grey[400] + : Colors.grey[600], + fontSize: 11, + letterSpacing: 0.3, + ), + ), + const SizedBox(height: 8), + // Progress bar with gradient + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Container( + height: 6, + decoration: BoxDecoration( + color: isDark + ? Colors.grey[800] + : Colors.grey[200], + borderRadius: BorderRadius.circular(4), + ), + child: LinearProgressIndicator( + value: widget.route.progress, + backgroundColor: Colors.transparent, + valueColor: AlwaysStoppedAnimation( + isCompleted + ? SvrntyColors.statusCompleted + : SvrntyColors.crimsonRed, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart deleted file mode 100644 index 5493696..0000000 --- a/lib/l10n/app_localizations.dart +++ /dev/null @@ -1,368 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:intl/intl.dart' as intl; - -import 'app_localizations_en.dart'; -import 'app_localizations_fr.dart'; - -// ignore_for_file: type=lint - -/// Callers can lookup localized strings with an instance of AppLocalizations -/// returned by `AppLocalizations.of(context)`. -/// -/// Applications need to include `AppLocalizations.delegate()` in their app's -/// `localizationDelegates` list, and the locales they support in the app's -/// `supportedLocales` list. For example: -/// -/// ```dart -/// import 'l10n/app_localizations.dart'; -/// -/// return MaterialApp( -/// localizationsDelegates: AppLocalizations.localizationsDelegates, -/// supportedLocales: AppLocalizations.supportedLocales, -/// home: MyApplicationHome(), -/// ); -/// ``` -/// -/// ## Update pubspec.yaml -/// -/// Please make sure to update your pubspec.yaml to include the following -/// packages: -/// -/// ```yaml -/// dependencies: -/// # Internationalization support. -/// flutter_localizations: -/// sdk: flutter -/// intl: any # Use the pinned version from flutter_localizations -/// -/// # Rest of dependencies -/// ``` -/// -/// ## iOS Applications -/// -/// iOS applications define key application metadata, including supported -/// locales, in an Info.plist file that is built into the application bundle. -/// To configure the locales supported by your app, you’ll need to edit this -/// file. -/// -/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. -/// Then, in the Project Navigator, open the Info.plist file under the Runner -/// project’s Runner folder. -/// -/// Next, select the Information Property List item, select Add Item from the -/// Editor menu, then select Localizations from the pop-up menu. -/// -/// Select and expand the newly-created Localizations item then, for each -/// locale your application supports, add a new item and select the locale -/// you wish to add from the pop-up menu in the Value field. This list should -/// be consistent with the languages listed in the AppLocalizations.supportedLocales -/// property. -abstract class AppLocalizations { - AppLocalizations(String locale) - : localeName = intl.Intl.canonicalizedLocale(locale.toString()); - - final String localeName; - - static AppLocalizations of(BuildContext context) { - return Localizations.of(context, AppLocalizations)!; - } - - static const LocalizationsDelegate delegate = - _AppLocalizationsDelegate(); - - /// A list of this localizations delegate along with the default localizations - /// delegates. - /// - /// Returns a list of localizations delegates containing this delegate along with - /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, - /// and GlobalWidgetsLocalizations.delegate. - /// - /// Additional delegates can be added by appending to this list in - /// MaterialApp. This list does not have to be used at all if a custom list - /// of delegates is preferred or required. - static const List> localizationsDelegates = - >[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; - - /// A list of this localizations delegate's supported locales. - static const List supportedLocales = [ - Locale('en'), - Locale('fr'), - ]; - - /// No description provided for @appTitle. - /// - /// In en, this message translates to: - /// **'Plan B Logistics'** - String get appTitle; - - /// No description provided for @appDescription. - /// - /// In en, this message translates to: - /// **'Delivery Management System'** - String get appDescription; - - /// No description provided for @loginWithKeycloak. - /// - /// In en, this message translates to: - /// **'Login with Keycloak'** - String get loginWithKeycloak; - - /// No description provided for @deliveryRoutes. - /// - /// In en, this message translates to: - /// **'Delivery Routes'** - String get deliveryRoutes; - - /// No description provided for @routes. - /// - /// In en, this message translates to: - /// **'Routes'** - String get routes; - - /// No description provided for @deliveries. - /// - /// In en, this message translates to: - /// **'Deliveries'** - String get deliveries; - - /// No description provided for @settings. - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - - /// No description provided for @profile. - /// - /// In en, this message translates to: - /// **'Profile'** - String get profile; - - /// No description provided for @logout. - /// - /// In en, this message translates to: - /// **'Logout'** - String get logout; - - /// No description provided for @completed. - /// - /// In en, this message translates to: - /// **'Completed'** - String get completed; - - /// No description provided for @pending. - /// - /// In en, this message translates to: - /// **'Pending'** - String get pending; - - /// No description provided for @todo. - /// - /// In en, this message translates to: - /// **'To Do'** - String get todo; - - /// No description provided for @delivered. - /// - /// In en, this message translates to: - /// **'Delivered'** - String get delivered; - - /// No description provided for @newCustomer. - /// - /// In en, this message translates to: - /// **'New Customer'** - String get newCustomer; - - /// No description provided for @items. - /// - /// In en, this message translates to: - /// **'{count} items'** - String items(int count); - - /// No description provided for @moneyCurrency. - /// - /// In en, this message translates to: - /// **'{amount} MAD'** - String moneyCurrency(double amount); - - /// No description provided for @call. - /// - /// In en, this message translates to: - /// **'Call'** - String get call; - - /// No description provided for @map. - /// - /// In en, this message translates to: - /// **'Map'** - String get map; - - /// No description provided for @more. - /// - /// In en, this message translates to: - /// **'More'** - String get more; - - /// No description provided for @markAsCompleted. - /// - /// In en, this message translates to: - /// **'Mark as Completed'** - String get markAsCompleted; - - /// No description provided for @markAsUncompleted. - /// - /// In en, this message translates to: - /// **'Mark as Uncompleted'** - String get markAsUncompleted; - - /// No description provided for @uploadPhoto. - /// - /// In en, this message translates to: - /// **'Upload Photo'** - String get uploadPhoto; - - /// No description provided for @viewDetails. - /// - /// In en, this message translates to: - /// **'View Details'** - String get viewDetails; - - /// No description provided for @deliverySuccessful. - /// - /// In en, this message translates to: - /// **'Delivery marked as completed'** - String get deliverySuccessful; - - /// No description provided for @deliveryFailed. - /// - /// In en, this message translates to: - /// **'Failed to mark delivery'** - String get deliveryFailed; - - /// No description provided for @noDeliveries. - /// - /// In en, this message translates to: - /// **'No deliveries'** - String get noDeliveries; - - /// No description provided for @noRoutes. - /// - /// In en, this message translates to: - /// **'No routes available'** - String get noRoutes; - - /// No description provided for @error. - /// - /// In en, this message translates to: - /// **'Error: {message}'** - String error(String message); - - /// No description provided for @retry. - /// - /// In en, this message translates to: - /// **'Retry'** - String get retry; - - /// No description provided for @authenticationRequired. - /// - /// In en, this message translates to: - /// **'Authentication required'** - String get authenticationRequired; - - /// No description provided for @phoneCall. - /// - /// In en, this message translates to: - /// **'Call customer'** - String get phoneCall; - - /// No description provided for @navigateToAddress. - /// - /// In en, this message translates to: - /// **'Show on map'** - String get navigateToAddress; - - /// No description provided for @language. - /// - /// In en, this message translates to: - /// **'Language'** - String get language; - - /// No description provided for @english. - /// - /// In en, this message translates to: - /// **'English'** - String get english; - - /// No description provided for @french. - /// - /// In en, this message translates to: - /// **'French'** - String get french; - - /// No description provided for @appVersion. - /// - /// In en, this message translates to: - /// **'App Version'** - String get appVersion; - - /// No description provided for @about. - /// - /// In en, this message translates to: - /// **'About'** - String get about; - - /// No description provided for @fullName. - /// - /// In en, this message translates to: - /// **'{firstName} {lastName}'** - String fullName(String firstName, String lastName); - - /// No description provided for @completedDeliveries. - /// - /// In en, this message translates to: - /// **'{completed}/{total} completed'** - String completedDeliveries(int completed, int total); -} - -class _AppLocalizationsDelegate - extends LocalizationsDelegate { - const _AppLocalizationsDelegate(); - - @override - Future load(Locale locale) { - return SynchronousFuture(lookupAppLocalizations(locale)); - } - - @override - bool isSupported(Locale locale) => - ['en', 'fr'].contains(locale.languageCode); - - @override - bool shouldReload(_AppLocalizationsDelegate old) => false; -} - -AppLocalizations lookupAppLocalizations(Locale locale) { - // Lookup logic when only language code is specified. - switch (locale.languageCode) { - case 'en': - return AppLocalizationsEn(); - case 'fr': - return AppLocalizationsFr(); - } - - throw FlutterError( - 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.', - ); -} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart deleted file mode 100644 index 5ec892f..0000000 --- a/lib/l10n/app_localizations_en.dart +++ /dev/null @@ -1,137 +0,0 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; -import 'app_localizations.dart'; - -// ignore_for_file: type=lint - -/// The translations for English (`en`). -class AppLocalizationsEn extends AppLocalizations { - AppLocalizationsEn([String locale = 'en']) : super(locale); - - @override - String get appTitle => 'Plan B Logistics'; - - @override - String get appDescription => 'Delivery Management System'; - - @override - String get loginWithKeycloak => 'Login with Keycloak'; - - @override - String get deliveryRoutes => 'Delivery Routes'; - - @override - String get routes => 'Routes'; - - @override - String get deliveries => 'Deliveries'; - - @override - String get settings => 'Settings'; - - @override - String get profile => 'Profile'; - - @override - String get logout => 'Logout'; - - @override - String get completed => 'Completed'; - - @override - String get pending => 'Pending'; - - @override - String get todo => 'To Do'; - - @override - String get delivered => 'Delivered'; - - @override - String get newCustomer => 'New Customer'; - - @override - String items(int count) { - return '$count items'; - } - - @override - String moneyCurrency(double amount) { - return '$amount MAD'; - } - - @override - String get call => 'Call'; - - @override - String get map => 'Map'; - - @override - String get more => 'More'; - - @override - String get markAsCompleted => 'Mark as Completed'; - - @override - String get markAsUncompleted => 'Mark as Uncompleted'; - - @override - String get uploadPhoto => 'Upload Photo'; - - @override - String get viewDetails => 'View Details'; - - @override - String get deliverySuccessful => 'Delivery marked as completed'; - - @override - String get deliveryFailed => 'Failed to mark delivery'; - - @override - String get noDeliveries => 'No deliveries'; - - @override - String get noRoutes => 'No routes available'; - - @override - String error(String message) { - return 'Error: $message'; - } - - @override - String get retry => 'Retry'; - - @override - String get authenticationRequired => 'Authentication required'; - - @override - String get phoneCall => 'Call customer'; - - @override - String get navigateToAddress => 'Show on map'; - - @override - String get language => 'Language'; - - @override - String get english => 'English'; - - @override - String get french => 'French'; - - @override - String get appVersion => 'App Version'; - - @override - String get about => 'About'; - - @override - String fullName(String firstName, String lastName) { - return '$firstName $lastName'; - } - - @override - String completedDeliveries(int completed, int total) { - return '$completed/$total completed'; - } -} diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart deleted file mode 100644 index 5d5b701..0000000 --- a/lib/l10n/app_localizations_fr.dart +++ /dev/null @@ -1,137 +0,0 @@ -// ignore: unused_import -import 'package:intl/intl.dart' as intl; -import 'app_localizations.dart'; - -// ignore_for_file: type=lint - -/// The translations for French (`fr`). -class AppLocalizationsFr extends AppLocalizations { - AppLocalizationsFr([String locale = 'fr']) : super(locale); - - @override - String get appTitle => 'Plan B Logistique'; - - @override - String get appDescription => 'Systme de Gestion des Livraisons'; - - @override - String get loginWithKeycloak => 'Connexion avec Keycloak'; - - @override - String get deliveryRoutes => 'Itinraires de Livraison'; - - @override - String get routes => 'Itinraires'; - - @override - String get deliveries => 'Livraisons'; - - @override - String get settings => 'Paramtres'; - - @override - String get profile => 'Profil'; - - @override - String get logout => 'Dconnexion'; - - @override - String get completed => 'Livr'; - - @override - String get pending => 'En attente'; - - @override - String get todo => 'livrer'; - - @override - String get delivered => 'Livr'; - - @override - String get newCustomer => 'Nouveau Client'; - - @override - String items(int count) { - return '$count articles'; - } - - @override - String moneyCurrency(double amount) { - return '$amount MAD'; - } - - @override - String get call => 'Appeler'; - - @override - String get map => 'Carte'; - - @override - String get more => 'Plus'; - - @override - String get markAsCompleted => 'Marquer comme livr'; - - @override - String get markAsUncompleted => 'Marquer comme livrer'; - - @override - String get uploadPhoto => 'Tlcharger une photo'; - - @override - String get viewDetails => 'Voir les dtails'; - - @override - String get deliverySuccessful => 'Livraison marque comme complte'; - - @override - String get deliveryFailed => 'chec du marquage de la livraison'; - - @override - String get noDeliveries => 'Aucune livraison'; - - @override - String get noRoutes => 'Aucun itinraire disponible'; - - @override - String error(String message) { - return 'Erreur: $message'; - } - - @override - String get retry => 'Ressayer'; - - @override - String get authenticationRequired => 'Authentification requise'; - - @override - String get phoneCall => 'Appeler le client'; - - @override - String get navigateToAddress => 'Afficher sur la carte'; - - @override - String get language => 'Langue'; - - @override - String get english => 'English'; - - @override - String get french => 'Franais'; - - @override - String get appVersion => 'Version de l\'application'; - - @override - String get about => ' propos'; - - @override - String fullName(String firstName, String lastName) { - return '$firstName $lastName'; - } - - @override - String completedDeliveries(int completed, int total) { - return '$completed/$total livrs'; - } -} diff --git a/lib/pages/deliveries_page.dart b/lib/pages/deliveries_page.dart index 0045de4..e36d3b3 100644 --- a/lib/pages/deliveries_page.dart +++ b/lib/pages/deliveries_page.dart @@ -9,7 +9,8 @@ import '../models/delivery_commands.dart'; import '../utils/breakpoints.dart'; import '../utils/responsive.dart'; import '../components/map_sidebar_layout.dart'; -import '../components/delivery_map.dart'; +import '../components/dark_mode_map.dart'; +import '../components/delivery_list_item.dart'; class DeliveriesPage extends ConsumerStatefulWidget { final int routeFragmentId; @@ -62,7 +63,7 @@ class _DeliveriesPageState extends ConsumerState { .toList(); return MapSidebarLayout( - mapWidget: DeliveryMap( + mapWidget: DarkModeMapComponent( deliveries: deliveries, selectedDelivery: _selectedDelivery, onDeliverySelected: (delivery) { @@ -260,14 +261,16 @@ class DeliveryListView extends StatelessWidget { // Trigger refresh via provider }, child: ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 8), itemCount: deliveries.length, itemBuilder: (context, index) { final delivery = deliveries[index]; - return DeliveryCard( + return DeliveryListItem( delivery: delivery, isSelected: selectedDelivery?.id == delivery.id, onTap: () => onDeliverySelected(delivery), - onAction: onAction, + onCall: () => onAction(delivery, 'call'), + animationIndex: index, ); }, ), diff --git a/lib/pages/routes_page.dart b/lib/pages/routes_page.dart index b3f45c6..7a1aa25 100644 --- a/lib/pages/routes_page.dart +++ b/lib/pages/routes_page.dart @@ -4,6 +4,7 @@ import '../models/delivery_route.dart'; import '../providers/providers.dart'; import '../utils/breakpoints.dart'; import '../utils/responsive.dart'; +import '../components/premium_route_card.dart'; import 'deliveries_page.dart'; import 'settings_page.dart'; @@ -137,47 +138,18 @@ class RoutesPage extends ConsumerWidget { } Widget _buildRouteCard(BuildContext context, DeliveryRoute route) { - return Card( - child: InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => DeliveriesPage( - routeFragmentId: route.id, - routeName: route.name, - ), + return PremiumRouteCard( + route: route, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DeliveriesPage( + routeFragmentId: route.id, + routeName: route.name, ), - ); - }, - child: Padding( - padding: EdgeInsets.all(ResponsiveSpacing.md(context)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - route.name, - style: Theme.of(context).textTheme.titleLarge, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: ResponsiveSpacing.sm(context)), - Text( - '${route.deliveredCount}/${route.deliveriesCount} completed', - style: Theme.of(context).textTheme.bodySmall, - ), - SizedBox(height: ResponsiveSpacing.md(context)), - ClipRRect( - borderRadius: BorderRadius.circular(4), - child: LinearProgressIndicator( - value: route.progress, - minHeight: 8, - ), - ), - ], ), - ), - ), + ); + }, ); } } diff --git a/lib/theme.dart b/lib/theme.dart index 2e711ec..4b97fca 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -1,4 +1,12 @@ -import "package:flutter/material.dart"; +import 'package:flutter/material.dart'; +import 'theme/color_system.dart'; +import 'theme/spacing_system.dart'; +import 'theme/border_system.dart'; +import 'theme/shadow_system.dart'; +import 'theme/size_system.dart'; +import 'theme/animation_system.dart'; +import 'theme/typography_system.dart'; +import 'theme/component_themes.dart'; class MaterialTheme { final TextTheme textTheme; @@ -9,51 +17,51 @@ class MaterialTheme { static ColorScheme lightScheme() { return const ColorScheme( brightness: Brightness.light, - primary: Color(0xffC44D58), // Svrnty Crimson Red - surfaceTint: Color(0xffC44D58), + primary: Color(0xffDF2D45), // Svrnty Crimson Red (updated) + surfaceTint: Color(0xffDF2D45), onPrimary: Color(0xffffffff), - primaryContainer: Color(0xffffd8db), - onPrimaryContainer: Color(0xff8b3238), - secondary: Color(0xff475C6C), // Svrnty Slate Blue + primaryContainer: Color(0xffFFE0E5), + onPrimaryContainer: Color(0xff06080C), + secondary: Color(0xff3A4958), // Svrnty Dark Slate onSecondary: Color(0xffffffff), - secondaryContainer: Color(0xffd1dce7), - onSecondaryContainer: Color(0xff2e3d4a), - tertiary: Color(0xff5a4a6c), + secondaryContainer: Color(0xffD0DCE8), + onSecondaryContainer: Color(0xff06080C), + tertiary: Color(0xff1D2C39), // Svrnty Teal onTertiary: Color(0xffffffff), - tertiaryContainer: Color(0xffe0d3f2), - onTertiaryContainer: Color(0xff3d2f4d), - error: Color(0xffba1a1a), + tertiaryContainer: Color(0xffBFD5E3), + onTertiaryContainer: Color(0xff06080C), + error: Color(0xffEF4444), onError: Color(0xffffffff), - errorContainer: Color(0xffffdad6), - onErrorContainer: Color(0xff93000a), - surface: Color(0xfffafafa), - onSurface: Color(0xff1a1c1e), - onSurfaceVariant: Color(0xff43474e), - outline: Color(0xff74777f), - outlineVariant: Color(0xffc4c6cf), - shadow: Color(0xff000000), + errorContainer: Color(0xffFEE2E2), + onErrorContainer: Color(0xff7F1D1D), + surface: Color(0xffFAFAFC), + onSurface: Color(0xff06080C), + onSurfaceVariant: Color(0xff506576), + outline: Color(0xffAEB8BE), + outlineVariant: Color(0xffD1D5DB), + shadow: Color(0xff1A000000), scrim: Color(0xff000000), - inverseSurface: Color(0xff2f3033), - inversePrimary: Color(0xffffb3b9), - primaryFixed: Color(0xffffd8db), - onPrimaryFixed: Color(0xff410008), - primaryFixedDim: Color(0xffffb3b9), - onPrimaryFixedVariant: Color(0xff8b3238), - secondaryFixed: Color(0xffd1dce7), - onSecondaryFixed: Color(0xff0f1a24), - secondaryFixedDim: Color(0xffb5c0cb), - onSecondaryFixedVariant: Color(0xff2e3d4a), - tertiaryFixed: Color(0xffe0d3f2), - onTertiaryFixed: Color(0xff1f122f), - tertiaryFixedDim: Color(0xffc4b7d6), - onTertiaryFixedVariant: Color(0xff3d2f4d), + inverseSurface: Color(0xff06080C), + inversePrimary: Color(0xffFF6B7D), + primaryFixed: Color(0xffFFE0E5), + onPrimaryFixed: Color(0xff06080C), + primaryFixedDim: Color(0xffFFC0C9), + onPrimaryFixedVariant: Color(0xff8B1A2A), + secondaryFixed: Color(0xffD0DCE8), + onSecondaryFixed: Color(0xff06080C), + secondaryFixedDim: Color(0xffB0C4D8), + onSecondaryFixedVariant: Color(0xff3A4958), + tertiaryFixed: Color(0xffBFD5E3), + onTertiaryFixed: Color(0xff06080C), + tertiaryFixedDim: Color(0xff9FBDCF), + onTertiaryFixedVariant: Color(0xff1D2C39), surfaceDim: Color(0xffdadcde), surfaceBright: Color(0xfffafafa), surfaceContainerLowest: Color(0xffffffff), - surfaceContainerLow: Color(0xfff4f5f7), - surfaceContainer: Color(0xffeef0f2), - surfaceContainerHigh: Color(0xffe8eaec), - surfaceContainerHighest: Color(0xffe2e4e7), + surfaceContainerLow: Color(0xfff6f6f8), + surfaceContainer: Color(0xfff1f1f4), + surfaceContainerHigh: Color(0xffebebee), + surfaceContainerHighest: Color(0xffe5e5e8), ); } @@ -171,55 +179,55 @@ class MaterialTheme { return theme(lightHighContrastScheme()); } - // Svrnty Brand Colors - Dark Theme (Bold & Saturated) + // Svrnty Brand Colors - Dark Theme static ColorScheme darkScheme() { return const ColorScheme( brightness: Brightness.dark, - primary: Color(0xffF3574E), // Bold Svrnty Crimson Red (slightly desaturated) - surfaceTint: Color(0xffF3574E), + primary: Color(0xffDF2D45), // Svrnty Crimson Red + surfaceTint: Color(0xffFF6B7D), onPrimary: Color(0xffffffff), - primaryContainer: Color(0xffC44D58), // True brand crimson - onPrimaryContainer: Color(0xffffffff), - secondary: Color(0xff5A6F7D), // Rich Svrnty Slate Blue + primaryContainer: Color(0xff9C1A29), + onPrimaryContainer: Color(0xffFFE0E5), + secondary: Color(0xff506576), // Svrnty Slate Gray onSecondary: Color(0xffffffff), - secondaryContainer: Color(0xff475C6C), // True brand slate - onSecondaryContainer: Color(0xffffffff), - tertiary: Color(0xffA78BBF), // Richer purple + secondaryContainer: Color(0xff3A4958), + onSecondaryContainer: Color(0xffD0DCE8), + tertiary: Color(0xff5A8FA8), // Svrnty Teal variant onTertiary: Color(0xffffffff), - tertiaryContainer: Color(0xff8B6FA3), - onTertiaryContainer: Color(0xffffffff), - error: Color(0xffFF5449), - onError: Color(0xffffffff), - errorContainer: Color(0xffD32F2F), - onErrorContainer: Color(0xffffffff), - surface: Color(0xff1a1c1e), // Svrnty Dark Background - onSurface: Color(0xfff0f0f0), - onSurfaceVariant: Color(0xffc8cad0), - outline: Color(0xff8d9199), - outlineVariant: Color(0xff43474e), + tertiaryContainer: Color(0xff1D2C39), + onTertiaryContainer: Color(0xffBFD5E3), + error: Color(0xffFF6B6B), + onError: Color(0xff4C0707), + errorContainer: Color(0xff93000A), + onErrorContainer: Color(0xffFEE2E2), + surface: Color(0xff0A0C10), // Svrnty Dark Background + onSurface: Color(0xffF0F0F2), + onSurfaceVariant: Color(0xffBFC3C8), + outline: Color(0xff6B7280), + outlineVariant: Color(0xff374151), shadow: Color(0xff000000), scrim: Color(0xff000000), - inverseSurface: Color(0xffe2e4e7), - inversePrimary: Color(0xffC44D58), - primaryFixed: Color(0xffFFD8DB), - onPrimaryFixed: Color(0xff2d0008), - primaryFixedDim: Color(0xffF3574E), - onPrimaryFixedVariant: Color(0xffffffff), - secondaryFixed: Color(0xffD1DCE7), - onSecondaryFixed: Color(0xff0f1a24), - secondaryFixedDim: Color(0xff5A6F7D), - onSecondaryFixedVariant: Color(0xffffffff), - tertiaryFixed: Color(0xffE0D3F2), - onTertiaryFixed: Color(0xff1f122f), - tertiaryFixedDim: Color(0xffA78BBF), - onTertiaryFixedVariant: Color(0xffffffff), - surfaceDim: Color(0xff1a1c1e), + inverseSurface: Color(0xffE2E2E6), + inversePrimary: Color(0xffDF2D45), + primaryFixed: Color(0xffFFE0E5), + onPrimaryFixed: Color(0xff3D0009), + primaryFixedDim: Color(0xffFF6B7D), + onPrimaryFixedVariant: Color(0xff9C1A29), + secondaryFixed: Color(0xffD0DCE8), + onSecondaryFixed: Color(0xff06080C), + secondaryFixedDim: Color(0xff506576), + onSecondaryFixedVariant: Color(0xff3A4958), + tertiaryFixed: Color(0xffBFD5E3), + onTertiaryFixed: Color(0xff001219), + tertiaryFixedDim: Color(0xff5A8FA8), + onTertiaryFixedVariant: Color(0xff1D2C39), + surfaceDim: Color(0xff0A0C10), surfaceBright: Color(0xff404244), - surfaceContainerLowest: Color(0xff0f1113), - surfaceContainerLow: Color(0xff1f2123), - surfaceContainer: Color(0xff23252a), - surfaceContainerHigh: Color(0xff2d2f35), - surfaceContainerHighest: Color(0xff383940), + surfaceContainerLowest: Color(0xff040507), + surfaceContainerLow: Color(0xff0F1114), + surfaceContainer: Color(0xff14161A), + surfaceContainerHigh: Color(0xff1F2228), + surfaceContainerHighest: Color(0xff2A2D34), ); } @@ -338,34 +346,128 @@ class MaterialTheme { } - ThemeData theme(ColorScheme colorScheme) => ThemeData( - useMaterial3: true, - brightness: colorScheme.brightness, - colorScheme: colorScheme, - textTheme: const TextTheme( - displayLarge: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.bold), - displayMedium: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.bold), - displaySmall: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.bold), - headlineLarge: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w600), - headlineMedium: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w600), - headlineSmall: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w600), - titleLarge: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w600), - titleMedium: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w500), - titleSmall: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w500), - bodyLarge: TextStyle(fontFamily: 'Montserrat'), - bodyMedium: TextStyle(fontFamily: 'Montserrat'), - bodySmall: TextStyle(fontFamily: 'Montserrat'), - labelLarge: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w500), - labelMedium: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w500), - labelSmall: TextStyle(fontFamily: 'Montserrat', fontWeight: FontWeight.w500), - ).apply( - bodyColor: colorScheme.onSurface, - displayColor: colorScheme.onSurface, - ), - fontFamily: 'Montserrat', - scaffoldBackgroundColor: colorScheme.surface, - canvasColor: colorScheme.surface, - ); + ThemeData theme(ColorScheme colorScheme) { + return ThemeData( + useMaterial3: true, + brightness: colorScheme.brightness, + colorScheme: colorScheme, + fontFamily: 'Montserrat', + scaffoldBackgroundColor: colorScheme.surface, + canvasColor: colorScheme.surface, + textTheme: const TextTheme( + displayLarge: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w700, + fontSize: 57, + letterSpacing: -0.5, + ), + displayMedium: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w700, + fontSize: 45, + letterSpacing: -0.5, + ), + displaySmall: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w700, + fontSize: 36, + letterSpacing: -0.25, + ), + headlineLarge: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w600, + fontSize: 32, + letterSpacing: -0.25, + ), + headlineMedium: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w600, + fontSize: 28, + letterSpacing: 0, + ), + headlineSmall: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w600, + fontSize: 24, + letterSpacing: 0, + ), + titleLarge: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w600, + fontSize: 22, + letterSpacing: 0, + ), + titleMedium: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 16, + letterSpacing: 0.15, + ), + titleSmall: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 14, + letterSpacing: 0.1, + ), + bodyLarge: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w400, + fontSize: 16, + letterSpacing: 0.5, + ), + bodyMedium: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w400, + fontSize: 14, + letterSpacing: 0.25, + ), + bodySmall: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w400, + fontSize: 12, + letterSpacing: 0.4, + ), + labelLarge: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 14, + letterSpacing: 0.1, + ), + labelMedium: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 12, + letterSpacing: 0.5, + ), + labelSmall: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 11, + letterSpacing: 0.5, + ), + ).apply( + bodyColor: colorScheme.onSurface, + displayColor: colorScheme.onSurface, + ), + // Component Themes + cardTheme: ComponentThemes.cardTheme(colorScheme), + appBarTheme: ComponentThemes.appBarTheme(colorScheme), + filledButtonTheme: ComponentThemes.filledButtonTheme(colorScheme), + elevatedButtonTheme: ComponentThemes.elevatedButtonTheme(colorScheme), + outlinedButtonTheme: ComponentThemes.outlinedButtonTheme(colorScheme), + inputDecorationTheme: ComponentThemes.inputDecorationTheme(colorScheme), + snackBarTheme: ComponentThemes.snackBarTheme(colorScheme), + dialogTheme: ComponentThemes.dialogTheme(colorScheme), + bottomNavigationBarTheme: + ComponentThemes.bottomNavigationBarTheme(colorScheme), + chipTheme: ComponentThemes.chipTheme(colorScheme), + progressIndicatorTheme: + ComponentThemes.progressIndicatorTheme(colorScheme), + floatingActionButtonTheme: + ComponentThemes.floatingActionButtonTheme(colorScheme), + sliderTheme: ComponentThemes.sliderTheme(colorScheme), + ); + } List get extendedColors => [ diff --git a/lib/theme/animation_system.dart b/lib/theme/animation_system.dart new file mode 100644 index 0000000..3df2fb6 --- /dev/null +++ b/lib/theme/animation_system.dart @@ -0,0 +1,272 @@ +import 'package:flutter/material.dart'; + +/// Svrnty Animation System +/// Complete animation configuration with durations, curves, and stagger delays +class AppAnimations { + // Prevent instantiation + AppAnimations._(); + + // ============================================ + // ANIMATION DURATION CONSTANTS + // ============================================ + + /// Fast animation duration (200ms) - Button press, hover, quick interactions + static const Duration durationFast = Duration(milliseconds: 200); + + /// Normal animation duration (300ms) - Default animations, fade, slide + static const Duration durationNormal = Duration(milliseconds: 300); + + /// Slow animation duration (500ms) - Prominent animations, entrance, transitions + static const Duration durationSlow = Duration(milliseconds: 500); + + /// Extra slow animation duration (800ms) - Slow, attention-grabbing animations + static const Duration durationXSlow = Duration(milliseconds: 800); + + // ============================================ + // ANIMATION DURATION IN MILLISECONDS + // ============================================ + + /// Fast animation milliseconds (200ms) + static const int durationFastMs = 200; + + /// Normal animation milliseconds (300ms) + static const int durationNormalMs = 300; + + /// Slow animation milliseconds (500ms) + static const int durationSlowMs = 500; + + /// Extra slow animation milliseconds (800ms) + static const int durationXSlowMs = 800; + + // ============================================ + // ANIMATION CURVES + // ============================================ + + /// Ease In - Slow start, fast end (decelerate) + static const Curve curveEaseIn = Curves.easeIn; + + /// Ease Out - Fast start, slow end (decelerate) + static const Curve curveEaseOut = Curves.easeOut; + + /// Ease In Out - Smooth both ends (decelerate at start and end) + static const Curve curveEaseInOut = Curves.easeInOut; + + /// Ease In Back - Back in, bounce effect + static const Curve curveEaseInBack = Curves.easeInBack; + + /// Ease Out Back - Back out, bounce effect + static const Curve curveEaseOutBack = Curves.easeOutBack; + + /// Elastic/Bouncy effect + static const Curve curveElastic = Curves.elasticOut; + + /// Bouncing motion + static const Curve curveBounce = Curves.bounceOut; + + /// Fast Out Slow In - Material standard curve + static const Curve curveFastOutSlowIn = Curves.fastOutSlowIn; + + /// Deceleration curve (accelerate) + static const Curve curveAccelerate = Curves.decelerate; + + /// Linear curve (no acceleration/deceleration) + static const Curve curveLinear = Curves.linear; + + // ============================================ + // STAGGER DELAYS (FOR LIST ANIMATIONS) + // ============================================ + + /// Default stagger delay for list items (50ms) + static const Duration staggerDelay = Duration(milliseconds: 50); + + /// Quick stagger delay (30ms) + static const Duration staggerDelayFast = Duration(milliseconds: 30); + + /// Slow stagger delay (100ms) + static const Duration staggerDelaySlow = Duration(milliseconds: 100); + + /// Stagger delay in milliseconds + static const int staggerDelayMs = 50; + + // ============================================ + // SCALE ANIMATION CONSTANTS + // ============================================ + + /// Normal scale (1.0) + static const double scaleNormal = 1.0; + + /// Hover/Light scale (1.02) + static const double scaleHover = 1.02; + + /// Pressed/Active scale (0.98) + static const double scalePressed = 0.98; + + /// Animation scale (0.9) + static const double scaleAnimation = 0.9; + + // ============================================ + // OFFSET ANIMATION CONSTANTS + // ============================================ + + /// Slide offset - Small (8px) + static const Offset offsetSm = Offset(0, 8); + + /// Slide offset - Medium (16px) + static const Offset offsetMd = Offset(0, 16); + + /// Slide offset - Large (20px) + static const Offset offsetLg = Offset(0, 20); + + /// Slide offset - Extra large (32px) + static const Offset offsetXl = Offset(0, 32); + + /// Floating offset - Up (10px) + static const Offset offsetFloating = Offset(0, -10); + + // ============================================ + // OPACITY ANIMATION CONSTANTS + // ============================================ + + /// Full opacity + static const double opacityFull = 1.0; + + /// Half opacity + static const double opacityHalf = 0.5; + + /// Subtle opacity (70%) + static const double opacitySubtle = 0.7; + + /// Dim opacity (50%) + static const double opacityDim = 0.5; + + /// Fade out opacity (30%) + static const double opacityFadeOut = 0.3; + + /// Invisible opacity (0%) + static const double opacityInvisible = 0.0; + + // ============================================ + // ANIMATION PRESETS + // ============================================ + + /// Scale animation preset (button press) - note: CurvedAnimation cannot be const + // Use this pattern in your widgets instead: + // CurvedAnimation(parent: controller, curve: Curves.easeInOut) + + // ============================================ + // ROTATION ANIMATION CONSTANTS + // ============================================ + + /// Full rotation (360 degrees) + static const double rotationFull = 1.0; + + /// Half rotation (180 degrees) + static const double rotationHalf = 0.5; + + /// Quarter rotation (90 degrees) + static const double rotationQuarter = 0.25; + + // ============================================ + // UTILITY METHODS + // ============================================ + + /// Get duration based on animation intensity + static Duration getDuration(AnimationIntensity intensity) { + switch (intensity) { + case AnimationIntensity.fast: + return durationFast; + case AnimationIntensity.normal: + return durationNormal; + case AnimationIntensity.slow: + return durationSlow; + case AnimationIntensity.xSlow: + return durationXSlow; + } + } + + /// Get curve based on animation type + static Curve getCurve(AnimationType type) { + switch (type) { + case AnimationType.easeIn: + return curveEaseIn; + case AnimationType.easeOut: + return curveEaseOut; + case AnimationType.easeInOut: + return curveEaseInOut; + case AnimationType.elastic: + return curveElastic; + case AnimationType.bounce: + return curveBounce; + case AnimationType.linear: + return curveLinear; + } + } + + /// Get stagger delay for list index + static Duration getStaggerDelay(int index, {bool fast = false}) { + final baseDelay = fast ? staggerDelayFast : staggerDelay; + return Duration( + milliseconds: baseDelay.inMilliseconds * index, + ); + } + + /// Get scale value based on interaction state + static double getScale(InteractionState state) { + switch (state) { + case InteractionState.normal: + return scaleNormal; + case InteractionState.hover: + return scaleHover; + case InteractionState.pressed: + return scalePressed; + } + } +} + +/// Animation intensity levels +enum AnimationIntensity { + /// 200ms - Quick interactions + fast, + + /// 300ms - Standard animations + normal, + + /// 500ms - Prominent animations + slow, + + /// 800ms - Slow, attention-grabbing animations + xSlow, +} + +/// Animation types/curves +enum AnimationType { + /// Ease In curve + easeIn, + + /// Ease Out curve + easeOut, + + /// Ease In Out curve + easeInOut, + + /// Elastic/bouncy curve + elastic, + + /// Bounce curve + bounce, + + /// Linear curve + linear, +} + +/// Interaction states for animation +enum InteractionState { + /// Normal/default state + normal, + + /// Hover state + hover, + + /// Pressed/active state + pressed, +} diff --git a/lib/theme/border_system.dart b/lib/theme/border_system.dart new file mode 100644 index 0000000..dde5b6b --- /dev/null +++ b/lib/theme/border_system.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; + +/// Svrnty Border System - Border Radius Constants +/// All border radius values follow a strict 4px grid +class AppBorders { + // Prevent instantiation + AppBorders._(); + + // ============================================ + // BORDER RADIUS VALUES (4px Grid) + // ============================================ + + /// Extra small border radius (4px) - unit × 1 + /// Used for: Subtle rounding on chips, tags, small elements + static const double radiusXs = 4.0; + + /// Small border radius (8px) - unit × 2 + /// Used for: Buttons, inputs, small cards + static const double radiusSm = 8.0; + + /// Medium border radius (12px) - unit × 3 + /// Used for: Cards, dropdowns, standard components + static const double radiusMd = 12.0; + + /// Large border radius (16px) - unit × 4 + /// Used for: Dialogs, large cards, prominent containers + static const double radiusLg = 16.0; + + /// Extra large border radius (24px) - unit × 6 + /// Used for: Containers, large surfaces + static const double radiusXl = 24.0; + + /// Fully rounded border radius (999px) + /// Used for: Pills, badges, fully circular elements + static const double radiusRound = 999.0; + + // ============================================ + // BORDER RADIUS - BORDERRADIUS OBJECTS + // ============================================ + + /// BorderRadius.circular(4px) + static const BorderRadius circularXs = BorderRadius.all(Radius.circular(radiusXs)); + + /// BorderRadius.circular(8px) + static const BorderRadius circularSm = BorderRadius.all(Radius.circular(radiusSm)); + + /// BorderRadius.circular(12px) + static const BorderRadius circularMd = BorderRadius.all(Radius.circular(radiusMd)); + + /// BorderRadius.circular(16px) + static const BorderRadius circularLg = BorderRadius.all(Radius.circular(radiusLg)); + + /// BorderRadius.circular(24px) + static const BorderRadius circularXl = BorderRadius.all(Radius.circular(radiusXl)); + + /// BorderRadius.circular(999px) - Fully rounded + static const BorderRadius circularRound = BorderRadius.all(Radius.circular(radiusRound)); + + // ============================================ + // BORDER RADIUS - TOP ONLY (for bottom sheets) + // ============================================ + + /// BorderRadius with top corners rounded (8px) + static const BorderRadius topSmallRadius = BorderRadius.only( + topLeft: Radius.circular(radiusSm), + topRight: Radius.circular(radiusSm), + ); + + /// BorderRadius with top corners rounded (12px) + static const BorderRadius topMediumRadius = BorderRadius.only( + topLeft: Radius.circular(radiusMd), + topRight: Radius.circular(radiusMd), + ); + + /// BorderRadius with top corners rounded (16px) + static const BorderRadius topLargeRadius = BorderRadius.only( + topLeft: Radius.circular(radiusLg), + topRight: Radius.circular(radiusLg), + ); + + // ============================================ + // COMPONENT BORDER RADIUS MAPPING + // ============================================ + + /// Button border radius (8px - radiusSm) + static const double buttonRadius = radiusSm; + + /// Input field border radius (8px - radiusSm) + static const double inputRadius = radiusSm; + + /// Card border radius (12px - radiusMd) + static const double cardRadius = radiusMd; + + /// Dialog border radius (16px - radiusLg) + static const double dialogRadius = radiusLg; + + /// Chip border radius (999px - radiusRound, fully rounded) + static const double chipRadius = radiusRound; + + /// Bottom sheet border radius (16px - radiusLg) + static const double bottomSheetRadius = radiusLg; + + /// Dropdown border radius (8px - radiusSm) + static const double dropdownRadius = radiusSm; + + /// Small component border radius (4px - radiusXs) + static const double smallComponentRadius = radiusXs; + + // ============================================ + // BORDER RADIUS SHAPE OBJECTS + // ============================================ + + /// RoundedRectangleBorder for buttons (8px radius) + static const ShapeBorder buttonShape = RoundedRectangleBorder( + borderRadius: circularSm, + ); + + /// RoundedRectangleBorder for cards (12px radius) + static const ShapeBorder cardShape = RoundedRectangleBorder( + borderRadius: circularMd, + ); + + /// RoundedRectangleBorder for dialogs (16px radius) + static const ShapeBorder dialogShape = RoundedRectangleBorder( + borderRadius: circularLg, + ); + + // ============================================ + // COMPONENT BORDER RADIUS OBJECTS + // ============================================ + + /// Small component border radius (4px - Radius object) + static const Radius smallRadius = Radius.circular(radiusXs); + + /// Button border radius (8px - Radius object) + static const Radius buttonBorderRadius = Radius.circular(radiusSm); + + /// Card border radius (12px - Radius object) + static const Radius cardBorderRadius = Radius.circular(radiusMd); + + /// Dialog border radius (16px - Radius object) + static const Radius dialogBorderRadius = Radius.circular(radiusLg); + + /// Fully rounded (999px - Radius object) + static const Radius fullRoundRadius = Radius.circular(radiusRound); +} diff --git a/lib/theme/color_system.dart b/lib/theme/color_system.dart new file mode 100644 index 0000000..f684f11 --- /dev/null +++ b/lib/theme/color_system.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; + +/// Svrnty Brand Color System +/// Complete color palette following Material Design 3 specifications +class SvrntyColors { + // Prevent instantiation + SvrntyColors._(); + + // ============================================ + // CORE BRAND COLORS + // ============================================ + + /// Crimson Red - Primary accent and brand signature + static const Color crimsonRed = Color(0xDF2D45); + + /// Almost Black - Primary dark background + static const Color almostBlack = Color(0x06080C); + + /// Dark Slate - Secondary dark tone + static const Color darkSlate = Color(0x3A4958); + + /// Slate Gray - Mid-tone gray + static const Color slateGray = Color(0x506576); + + /// Teal - Tertiary accent + static const Color teal = Color(0x1D2C39); + + /// Light Gray - Neutral light + static const Color lightGray = Color(0xAEB8BE); + + // ============================================ + // SEMANTIC COLORS + // ============================================ + + /// Success - Green for positive actions and completed states + static const Color success = Color(0x22C55E); + + /// Warning - Amber for warnings and attention-needed states + static const Color warning = Color(0xF59E0B); + + /// Info - Blue for informational and in-progress states + static const Color info = Color(0x3B82F6); + + /// Error - Red for errors, failures, and destructive actions + static const Color error = Color(0xEF4444); + + // ============================================ + // DELIVERY STATUS COLORS + // ============================================ + + /// Status Pending - Awaiting action (Slate Gray) + static const Color statusPending = slateGray; + + /// Status In Progress - Currently being delivered (Blue) + static const Color statusInProgress = info; + + /// Status Completed - Successfully delivered (Green) + static const Color statusCompleted = success; + + /// Status Skipped - Skipped/passed delivery (Amber) + static const Color statusSkipped = warning; + + /// Status Failed - Failed delivery (Red) + static const Color statusFailed = error; + + // ============================================ + // SURFACE VARIANTS + // ============================================ + + /// Surface Elevated - Light elevated surface + static const Color surfaceElevated = Color(0xF5F7FA); + + /// Surface Subdued - Subdued light surface + static const Color surfaceSubdued = Color(0xE8EAEE); + + // ============================================ + // EXTENDED COLOR FAMILIES - SUCCESS (GREEN) + // ============================================ + + /// Success color light theme + static const Color successLight = Color(0x22C55E); + + /// Success on color light theme + static const Color onSuccessLight = Color(0xFFFFFF); + + /// Success container light theme + static const Color successContainerLight = Color(0xDCFCE7); + + /// Success on container light theme + static const Color onSuccessContainerLight = Color(0x14532D); + + /// Success color dark theme + static const Color successDark = Color(0x4ADE80); + + /// Success on color dark theme + static const Color onSuccessDark = Color(0x14532D); + + /// Success container dark theme + static const Color successContainerDark = Color(0x15803D); + + /// Success on container dark theme + static const Color onSuccessContainerDark = Color(0xDCFCE7); + + // ============================================ + // EXTENDED COLOR FAMILIES - WARNING (AMBER) + // ============================================ + + /// Warning color light theme + static const Color warningLight = Color(0xF59E0B); + + /// Warning on color light theme + static const Color onWarningLight = Color(0xFFFFFF); + + /// Warning container light theme + static const Color warningContainerLight = Color(0xFEF3C7); + + /// Warning on container light theme + static const Color onWarningContainerLight = Color(0x78350F); + + /// Warning color dark theme + static const Color warningDark = Color(0xFBBF24); + + /// Warning on color dark theme + static const Color onWarningDark = Color(0x78350F); + + /// Warning container dark theme + static const Color warningContainerDark = Color(0xD97706); + + /// Warning on container dark theme + static const Color onWarningContainerDark = Color(0xFEF3C7); + + // ============================================ + // EXTENDED COLOR FAMILIES - INFO (BLUE) + // ============================================ + + /// Info color light theme + static const Color infoLight = Color(0x3B82F6); + + /// Info on color light theme + static const Color onInfoLight = Color(0xFFFFFF); + + /// Info container light theme + static const Color infoContainerLight = Color(0xDEEEFF); + + /// Info on container light theme + static const Color onInfoContainerLight = Color(0x003DA1); + + /// Info color dark theme + static const Color infoDark = Color(0x90CAF9); + + /// Info on color dark theme + static const Color onInfoDark = Color(0x003DA1); + + /// Info container dark theme + static const Color infoContainerDark = Color(0x0D47A1); + + /// Info on container dark theme + static const Color onInfoContainerDark = Color(0xDEEEFF); +} diff --git a/lib/theme/component_themes.dart b/lib/theme/component_themes.dart new file mode 100644 index 0000000..1faba24 --- /dev/null +++ b/lib/theme/component_themes.dart @@ -0,0 +1,262 @@ +import 'package:flutter/material.dart'; +import 'spacing_system.dart'; +import 'border_system.dart'; +import 'shadow_system.dart'; +import 'size_system.dart'; + +/// Component Theme Data Builders +/// Centralized component theme definitions for consistent styling +class ComponentThemes { + static CardThemeData cardTheme(ColorScheme colorScheme) { + return CardThemeData( + elevation: AppShadow.cardElevation, + shadowColor: AppShadow.getShadowColor(colorScheme.brightness), + shape: RoundedRectangleBorder( + borderRadius: AppBorders.circularMd, + ), + clipBehavior: Clip.antiAlias, + color: colorScheme.surface, + ); + } + + static AppBarTheme appBarTheme(ColorScheme colorScheme) { + return AppBarTheme( + backgroundColor: colorScheme.surface, + foregroundColor: colorScheme.onSurface, + elevation: AppShadow.appBarElevation, + centerTitle: false, + iconTheme: IconThemeData( + color: colorScheme.onSurface, + size: AppSizes.iconMd, + ), + titleTextStyle: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w600, + fontSize: 20, + color: colorScheme.onSurface, + ), + ); + } + + static FilledButtonThemeData filledButtonTheme(ColorScheme colorScheme) { + return FilledButtonThemeData( + style: FilledButton.styleFrom( + backgroundColor: colorScheme.primary, + foregroundColor: colorScheme.onPrimary, + elevation: AppShadow.elevationSm, + padding: AppSpacing.paddingButton, + shape: RoundedRectangleBorder( + borderRadius: AppBorders.circularSm, + ), + textStyle: const TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 14, + ), + ), + ); + } + + static ElevatedButtonThemeData elevatedButtonTheme(ColorScheme colorScheme) { + return ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: colorScheme.primary, + foregroundColor: colorScheme.onPrimary, + elevation: AppShadow.elevationSm, + padding: AppSpacing.paddingButton, + shape: RoundedRectangleBorder( + borderRadius: AppBorders.circularSm, + ), + textStyle: const TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 14, + ), + ), + ); + } + + static OutlinedButtonThemeData outlinedButtonTheme(ColorScheme colorScheme) { + return OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: colorScheme.primary, + side: BorderSide(color: colorScheme.outline), + padding: AppSpacing.paddingButton, + shape: RoundedRectangleBorder( + borderRadius: AppBorders.circularSm, + ), + textStyle: const TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 14, + ), + ), + ); + } + + static InputDecorationTheme inputDecorationTheme(ColorScheme colorScheme) { + return InputDecorationTheme( + filled: true, + fillColor: colorScheme.surfaceContainerHighest.withOpacity(0.5), + contentPadding: EdgeInsets.symmetric( + horizontal: AppSpacing.inputPadding, + vertical: AppSpacing.md, + ), + border: OutlineInputBorder( + borderRadius: AppBorders.circularSm, + borderSide: BorderSide( + color: colorScheme.outline.withOpacity(0.3), + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: AppBorders.circularSm, + borderSide: BorderSide( + color: colorScheme.outline.withOpacity(0.3), + width: 1, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: AppBorders.circularSm, + borderSide: BorderSide( + color: colorScheme.primary, + width: 2, + ), + ), + errorBorder: OutlineInputBorder( + borderRadius: AppBorders.circularSm, + borderSide: BorderSide( + color: colorScheme.error, + width: 1, + ), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: AppBorders.circularSm, + borderSide: BorderSide( + color: colorScheme.error, + width: 2, + ), + ), + labelStyle: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + color: colorScheme.onSurfaceVariant, + ), + hintStyle: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w400, + color: colorScheme.onSurfaceVariant.withOpacity(0.6), + ), + ); + } + + static SnackBarThemeData snackBarTheme(ColorScheme colorScheme) { + return SnackBarThemeData( + backgroundColor: colorScheme.inverseSurface, + contentTextStyle: TextStyle( + fontFamily: 'Montserrat', + color: colorScheme.onInverseSurface, + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: AppBorders.circularSm, + ), + ); + } + + static DialogThemeData dialogTheme(ColorScheme colorScheme) { + return DialogThemeData( + backgroundColor: colorScheme.surface, + elevation: AppShadow.dialogElevation, + shape: RoundedRectangleBorder( + borderRadius: AppBorders.circularLg, + ), + contentTextStyle: TextStyle( + fontFamily: 'Montserrat', + color: colorScheme.onSurface, + ), + ); + } + + static BottomNavigationBarThemeData bottomNavigationBarTheme( + ColorScheme colorScheme, + ) { + return BottomNavigationBarThemeData( + backgroundColor: colorScheme.surface, + selectedItemColor: colorScheme.primary, + unselectedItemColor: colorScheme.onSurfaceVariant, + showSelectedLabels: true, + showUnselectedLabels: true, + type: BottomNavigationBarType.fixed, + selectedLabelStyle: const TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + fontSize: 12, + ), + unselectedLabelStyle: const TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ); + } + + static ChipThemeData chipTheme(ColorScheme colorScheme) { + return ChipThemeData( + backgroundColor: colorScheme.surfaceContainerHighest, + deleteIconColor: colorScheme.onSurfaceVariant, + disabledColor: colorScheme.surfaceContainerHighest.withOpacity(0.38), + padding: EdgeInsets.symmetric( + horizontal: AppSpacing.sm, + vertical: AppSpacing.xs, + ), + shape: RoundedRectangleBorder( + borderRadius: AppBorders.circularSm, + ), + labelStyle: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + color: colorScheme.onSurfaceVariant, + ), + secondaryLabelStyle: TextStyle( + fontFamily: 'Montserrat', + fontWeight: FontWeight.w500, + color: colorScheme.onSurfaceVariant, + ), + brightness: colorScheme.brightness, + ); + } + + static ProgressIndicatorThemeData progressIndicatorTheme( + ColorScheme colorScheme, + ) { + return ProgressIndicatorThemeData( + color: colorScheme.primary, + linearTrackColor: colorScheme.surfaceContainerHighest, + circularTrackColor: colorScheme.surfaceContainerHighest, + ); + } + + static FloatingActionButtonThemeData floatingActionButtonTheme( + ColorScheme colorScheme, + ) { + return FloatingActionButtonThemeData( + backgroundColor: colorScheme.primary, + foregroundColor: colorScheme.onPrimary, + elevation: AppShadow.fabElevation, + shape: RoundedRectangleBorder( + borderRadius: AppBorders.circularMd, + ), + ); + } + + static SliderThemeData sliderTheme(ColorScheme colorScheme) { + return SliderThemeData( + trackHeight: 4.0, + activeTrackColor: colorScheme.primary, + inactiveTrackColor: colorScheme.surfaceContainerHighest, + thumbColor: colorScheme.primary, + overlayColor: colorScheme.primary.withOpacity(0.12), + valueIndicatorColor: colorScheme.primary, + ); + } +} diff --git a/lib/theme/gradient_system.dart b/lib/theme/gradient_system.dart new file mode 100644 index 0000000..658c0ea --- /dev/null +++ b/lib/theme/gradient_system.dart @@ -0,0 +1,332 @@ +import 'package:flutter/material.dart'; +import 'color_system.dart'; + +/// Svrnty Gradient System +/// Predefined gradients for status bars, progress indicators, and backgrounds +class AppGradients { + // Prevent instantiation + AppGradients._(); + + // ============================================ + // DELIVERY STATUS GRADIENTS + // ============================================ + + /// Pending status gradient (Slate Gray) + static const LinearGradient gradientStatusPending = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.statusPending, + Color(0x506576), // Slightly different shade for gradient effect + ], + ); + + /// In Progress status gradient (Blue/Info) + static const LinearGradient gradientStatusInProgress = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.statusInProgress, + Color(0x5B9BFF), // Lighter blue for gradient + ], + ); + + /// Completed status gradient (Green/Success) + static const LinearGradient gradientStatusCompleted = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.statusCompleted, + Color(0x4ADE80), // Lighter green for gradient + ], + ); + + /// Skipped status gradient (Amber/Warning) + static const LinearGradient gradientStatusSkipped = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.statusSkipped, + Color(0xFBBF24), // Lighter amber for gradient + ], + ); + + /// Failed status gradient (Red/Error) + static const LinearGradient gradientStatusFailed = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.statusFailed, + Color(0xFF7D7D), // Lighter red for gradient + ], + ); + + // ============================================ + // PROGRESS INDICATOR GRADIENTS + // ============================================ + + /// Progress bar gradient (Blue to transparent) + static LinearGradient gradientProgress({ + required Color color, + bool horizontal = true, + }) { + return LinearGradient( + begin: horizontal ? Alignment.centerLeft : Alignment.topCenter, + end: horizontal ? Alignment.centerRight : Alignment.bottomCenter, + colors: [ + color, + color.withOpacity(0.8), + ], + ); + } + + /// Success progress gradient + static const LinearGradient gradientProgressSuccess = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.success, + Color(0x4ADE80), // Lighter green + ], + ); + + /// Warning progress gradient + static const LinearGradient gradientProgressWarning = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.warning, + Color(0xFBBF24), // Lighter amber + ], + ); + + /// Error progress gradient + static const LinearGradient gradientProgressError = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.error, + Color(0xFF7D7D), // Lighter red + ], + ); + + /// Info progress gradient + static const LinearGradient gradientProgressInfo = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + SvrntyColors.info, + Color(0x5B9BFF), // Lighter blue + ], + ); + + // ============================================ + // BRAND GRADIENTS + // ============================================ + + /// Primary brand gradient (Crimson Red) + static const LinearGradient gradientPrimary = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + SvrntyColors.crimsonRed, + Color(0xC44D58), // Slightly darker shade + ], + ); + + /// Secondary brand gradient (Slate Blue) + static const LinearGradient gradientSecondary = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + SvrntyColors.darkSlate, + SvrntyColors.slateGray, + ], + ); + + /// Accent gradient (Crimson to Slate) + static const LinearGradient gradientAccent = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + SvrntyColors.crimsonRed, + SvrntyColors.darkSlate, + ], + ); + + // ============================================ + // BACKGROUND GRADIENTS + // ============================================ + + /// Light background gradient + static const LinearGradient gradientBackgroundLight = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFAFAFC), + Color(0xF5F7FA), + ], + ); + + /// Dark background gradient + static const LinearGradient gradientBackgroundDark = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0x1A1C1E), + Color(0x2A2D34), + ], + ); + + /// Elevated surface gradient (light) + static const LinearGradient gradientElevatedLight = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFFFFFF), + Color(0xF5F7FA), + ], + ); + + /// Elevated surface gradient (dark) + static const LinearGradient gradientElevatedDark = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0x2A2D34), + Color(0x1F2123), + ], + ); + + // ============================================ + // OVERLAY GRADIENTS + // ============================================ + + /// Dark overlay gradient (for images) + static const LinearGradient gradientOverlayDark = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0x00000000), // Transparent at top + Color(0x4D000000), // Dark at bottom + ], + ); + + /// Light overlay gradient + static const LinearGradient gradientOverlayLight = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0x00FFFFFF), // Transparent at top + Color(0x4DFFFFFF), // Light at bottom + ], + ); + + /// Vignette gradient (darkened edges) + static const RadialGradient gradientVignette = RadialGradient( + center: Alignment.center, + radius: 1.2, + colors: [ + Color(0x00000000), + Color(0x80000000), + ], + ); + + // ============================================ + // SHIMMER GRADIENTS (for loading states) + // ============================================ + + /// Shimmer gradient light theme + static const LinearGradient gradientShimmerLight = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color(0xFFFFFFFF), + Color(0x80F0F0F0), + Color(0xFFFFFFFF), + ], + stops: [0.1, 0.5, 0.9], + ); + + /// Shimmer gradient dark theme + static const LinearGradient gradientShimmerDark = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color(0xFF2A2D34), + Color(0x80383940), + Color(0xFF2A2D34), + ], + stops: [0.1, 0.5, 0.9], + ); + + // ============================================ + // UTILITY METHODS + // ============================================ + + /// Get status gradient based on delivery status + static LinearGradient getStatusGradient(String status) { + switch (status.toLowerCase()) { + case 'pending': + return gradientStatusPending; + case 'in_progress': + case 'inprogress': + return gradientStatusInProgress; + case 'completed': + case 'done': + return gradientStatusCompleted; + case 'skipped': + return gradientStatusSkipped; + case 'failed': + return gradientStatusFailed; + default: + return gradientStatusPending; + } + } + + /// Get progress gradient based on status + static LinearGradient getProgressGradient(String status) { + switch (status.toLowerCase()) { + case 'success': + return gradientProgressSuccess; + case 'warning': + return gradientProgressWarning; + case 'error': + return gradientProgressError; + case 'info': + return gradientProgressInfo; + default: + return gradientProgressSuccess; + } + } + + /// Create a custom gradient with opacity + static LinearGradient withOpacity( + LinearGradient gradient, + double opacity, + ) { + return LinearGradient( + begin: gradient.begin, + end: gradient.end, + colors: gradient.colors + .map((color) => color.withOpacity(opacity)) + .toList(), + stops: gradient.stops, + ); + } + + /// Create a directional gradient + static LinearGradient directional({ + required List colors, + required Alignment begin, + required Alignment end, + List? stops, + }) { + return LinearGradient( + begin: begin, + end: end, + colors: colors, + stops: stops, + ); + } +} diff --git a/lib/theme/shadow_system.dart b/lib/theme/shadow_system.dart new file mode 100644 index 0000000..5934bcd --- /dev/null +++ b/lib/theme/shadow_system.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; + +/// Svrnty Shadow & Elevation System +/// Comprehensive shadow and elevation definitions for light and dark themes +class AppShadow { + // Prevent instantiation + AppShadow._(); + + // ============================================ + // ELEVATION CONSTANTS + // ============================================ + + /// No elevation/shadow + static const double elevationNone = 0.0; + + /// Minimal depth elevation + static const double elevationXs = 1.0; + + /// Small shadow elevation (default for cards) + static const double elevationSm = 2.0; + + /// Medium shadow elevation (hover states) + static const double elevationMd = 4.0; + + /// Large shadow elevation + static const double elevationLg = 8.0; + + /// Extra large shadow elevation (dialogs, prominent surfaces) + static const double elevationXl = 16.0; + + // ============================================ + // SHADOW DEFINITIONS - LIGHT THEME + // ============================================ + + /// Light theme shadow color (10% opacity black) + static const Color _shadowColorLight = Color(0x1A000000); + + /// No shadow for light theme + static const List shadowNoneLight = []; + + /// Minimal shadow for light theme + static const List shadowXsLight = [ + BoxShadow( + color: _shadowColorLight, + blurRadius: 1, + offset: Offset(0, 1), + ), + ]; + + /// Small shadow for light theme (default for cards) + static const List shadowSmLight = [ + BoxShadow( + color: _shadowColorLight, + blurRadius: 2, + offset: Offset(0, 1), + ), + ]; + + /// Medium shadow for light theme (hover states) + static const List shadowMdLight = [ + BoxShadow( + color: _shadowColorLight, + blurRadius: 4, + offset: Offset(0, 2), + ), + ]; + + /// Large shadow for light theme + static const List shadowLgLight = [ + BoxShadow( + color: _shadowColorLight, + blurRadius: 8, + offset: Offset(0, 4), + ), + ]; + + /// Extra large shadow for light theme (dialogs) + static const List shadowXlLight = [ + BoxShadow( + color: _shadowColorLight, + blurRadius: 16, + offset: Offset(0, 8), + ), + ]; + + // ============================================ + // SHADOW DEFINITIONS - DARK THEME + // ============================================ + + /// Dark theme shadow color (pure black) + static const Color _shadowColorDark = Color(0x4D000000); + + /// No shadow for dark theme + static const List shadowNoneDark = []; + + /// Minimal shadow for dark theme + static const List shadowXsDark = [ + BoxShadow( + color: _shadowColorDark, + blurRadius: 1, + offset: Offset(0, 1), + ), + ]; + + /// Small shadow for dark theme (default for cards) + static const List shadowSmDark = [ + BoxShadow( + color: _shadowColorDark, + blurRadius: 2, + offset: Offset(0, 1), + ), + ]; + + /// Medium shadow for dark theme (hover states) + static const List shadowMdDark = [ + BoxShadow( + color: _shadowColorDark, + blurRadius: 4, + offset: Offset(0, 2), + ), + ]; + + /// Large shadow for dark theme + static const List shadowLgDark = [ + BoxShadow( + color: _shadowColorDark, + blurRadius: 8, + offset: Offset(0, 4), + ), + ]; + + /// Extra large shadow for dark theme (dialogs) + static const List shadowXlDark = [ + BoxShadow( + color: _shadowColorDark, + blurRadius: 16, + offset: Offset(0, 8), + ), + ]; + + // ============================================ + // SHADOW UTILITY METHODS + // ============================================ + + /// Get shadow list based on brightness and elevation level + static List getShadow( + Brightness brightness, + double elevation, + ) { + final isDark = brightness == Brightness.dark; + + switch (elevation) { + case elevationNone: + return isDark ? shadowNoneDark : shadowNoneLight; + case elevationXs: + return isDark ? shadowXsDark : shadowXsLight; + case elevationSm: + return isDark ? shadowSmDark : shadowSmLight; + case elevationMd: + return isDark ? shadowMdDark : shadowMdLight; + case elevationLg: + return isDark ? shadowLgDark : shadowLgLight; + case elevationXl: + return isDark ? shadowXlDark : shadowXlLight; + default: + return isDark ? shadowSmDark : shadowSmLight; + } + } + + /// Get shadow color based on brightness + static Color getShadowColor(Brightness brightness) { + return brightness == Brightness.dark ? _shadowColorDark : _shadowColorLight; + } + + // ============================================ + // COMPONENT ELEVATION MAPPING + // ============================================ + + /// Card elevation (2) + static const double cardElevation = elevationSm; + + /// Card hover elevation (4) + static const double cardHoverElevation = elevationMd; + + /// AppBar elevation (0 - flat design) + static const double appBarElevation = elevationNone; + + /// Dialog elevation (8) + static const double dialogElevation = elevationLg; + + /// FAB elevation (8) + static const double fabElevation = elevationLg; + + /// FAB hover elevation (16) + static const double fabHoverElevation = elevationXl; + + /// Bottom sheet elevation (8) + static const double bottomSheetElevation = elevationLg; + + /// Floating action button pressed elevation (12) + static const double fabPressedElevation = elevationXl; + + /// Menu/Dropdown elevation (8) + static const double menuElevation = elevationLg; + + /// Tooltip elevation (16) + static const double tooltipElevation = elevationXl; +} diff --git a/lib/theme/size_system.dart b/lib/theme/size_system.dart new file mode 100644 index 0000000..22e462a --- /dev/null +++ b/lib/theme/size_system.dart @@ -0,0 +1,236 @@ +import 'package:flutter/material.dart'; + +/// Svrnty Size System +/// Standard sizing constants for icons, buttons, containers, and other components +class AppSizes { + // Prevent instantiation + AppSizes._(); + + // ============================================ + // ICON SIZES + // ============================================ + + /// Extra small icon (16px) + static const double iconXs = 16.0; + + /// Small icon (20px) + static const double iconSm = 20.0; + + /// Standard icon size (24px) + static const double iconMd = 24.0; + + /// Large icon size (32px) + static const double iconLg = 32.0; + + /// Extra large icon (40px) + static const double iconXl = 40.0; + + /// Huge icon (48px) + static const double iconXxl = 48.0; + + /// Extra huge icon (56px) + static const double iconXxxl = 56.0; + + // ============================================ + // BUTTON SIZES + // ============================================ + + /// Small button height (32px) + static const double buttonHeightSm = 32.0; + + /// Medium button height (40px) - Default + static const double buttonHeightMd = 40.0; + + /// Large button height (48px) + static const double buttonHeightLg = 48.0; + + /// Extra large button height (56px) + static const double buttonHeightXl = 56.0; + + // ============================================ + // INPUT FIELD SIZES + // ============================================ + + /// Input field height + static const double inputHeight = 56.0; + + /// Compact input field height (no vertical padding) + static const double inputHeightCompact = 40.0; + + /// Input field min width + static const double inputMinWidth = 48.0; + + // ============================================ + // CONTAINER & LAYOUT SIZES + // ============================================ + + /// Standard card minimum height + static const double cardMinHeight = 80.0; + + /// Standard dialog max width (mobile) + static const double dialogMaxWidthMobile = 280.0; + + /// Standard dialog max width (tablet/desktop) + static const double dialogMaxWidthDesktop = 560.0; + + /// Maximum content width for centered layouts + static const double maxContentWidth = 1200.0; + + /// Compact content width (forms, focused layouts) + static const double compactContentWidth = 600.0; + + /// Standard container max width + static const double containerMaxWidth = 900.0; + + // ============================================ + // APPBAR & HEADER SIZES + // ============================================ + + /// Standard app bar height + static const double appBarHeight = 56.0; + + /// Large app bar height + static const double appBarHeightLarge = 72.0; + + /// Compact app bar height + static const double appBarHeightCompact = 48.0; + + // ============================================ + // BOTTOM SHEET SIZES + // ============================================ + + /// Bottom sheet max width + static const double bottomSheetMaxWidth = 540.0; + + /// Bottom sheet default height (auto) + static const double bottomSheetHeightAuto = 0.0; + + /// Bottom sheet half screen height + static const double bottomSheetHeightHalf = 0.5; + + /// Bottom sheet 3/4 screen height + static const double bottomSheetHeight3Quarter = 0.75; + + // ============================================ + // ELEVATION & Z-INDEX + // ============================================ + + /// Standard z-index for floating elements + static const int zIndexFloating = 100; + + /// Z-index for modals/dialogs + static const int zIndexModal = 50; + + /// Z-index for tooltips + static const int zIndexTooltip = 150; + + // ============================================ + // DIVIDER & LINE SIZES + // ============================================ + + /// Divider thickness + static const double dividerThickness = 1.0; + + /// Thin divider thickness (0.5px) + static const double dividerThicknessThin = 0.5; + + /// Thick divider thickness (2px) + static const double dividerThicknessThick = 2.0; + + /// Horizontal divider height + static const double dividerHeightHorizontal = 1.0; + + /// Vertical divider width + static const double dividerWidthVertical = 1.0; + + // ============================================ + // PROGRESS INDICATOR SIZES + // ============================================ + + /// Progress indicator thickness + static const double progressIndicatorThickness = 4.0; + + /// Circular progress indicator size + static const double circularProgressSize = 48.0; + + /// Linear progress indicator height (thin) + static const double linearProgressHeightThin = 2.0; + + /// Linear progress indicator height (standard) + static const double linearProgressHeightStandard = 4.0; + + /// Linear progress indicator height (thick) + static const double linearProgressHeightThick = 8.0; + + // ============================================ + // CHIP & BADGE SIZES + // ============================================ + + /// Chip height + static const double chipHeight = 32.0; + + /// Small chip height + static const double chipHeightSm = 24.0; + + /// Badge size (for counter badges) + static const double badgeSize = 24.0; + + /// Badge size (small) + static const double badgeSizeSm = 16.0; + + // ============================================ + // AVATAR SIZES + // ============================================ + + /// Small avatar size + static const double avatarSizeSm = 32.0; + + /// Medium avatar size + static const double avatarSizeMd = 48.0; + + /// Large avatar size + static const double avatarSizeLg = 64.0; + + /// Extra large avatar size + static const double avatarSizeXl = 80.0; + + // ============================================ + // RESPONSIVE SIZING + // ============================================ + + /// Minimum tap target size (Material guidelines - 48px) + static const double minTapTarget = 48.0; + + /// Minimum tap target size for desktop (36px) + static const double minTapTargetDesktop = 36.0; + + /// Standard element spacing + static const double elementSpacing = 8.0; + + /// Large element spacing + static const double elementSpacingLarge = 16.0; + + // ============================================ + // UTILITY METHODS + // ============================================ + + /// Get responsive button height based on device type + static double getButtonHeight(bool isCompact) { + return isCompact ? buttonHeightMd : buttonHeightLg; + } + + /// Get responsive dialog max width based on screen width + static double getDialogMaxWidth(double screenWidth) { + return screenWidth < 600 ? dialogMaxWidthMobile : dialogMaxWidthDesktop; + } + + /// Get responsive icon size + static double getIconSize({ + required bool isCompact, + required bool isLarge, + }) { + if (isLarge) return iconLg; + if (isCompact) return iconSm; + return iconMd; + } +} diff --git a/lib/theme/spacing_system.dart b/lib/theme/spacing_system.dart new file mode 100644 index 0000000..7ee1e21 --- /dev/null +++ b/lib/theme/spacing_system.dart @@ -0,0 +1,296 @@ +import 'package:flutter/material.dart'; + +/// Svrnty Spacing System - 4px Grid +/// All spacing, sizing, and border radius values follow a strict 4px grid +class AppSpacing { + // Prevent instantiation + AppSpacing._(); + + // ============================================ + // BASE SPACING SCALE (4px grid) + // ============================================ + + /// Extra small spacing (4px) - unit × 1 + static const double xs = 4.0; + + /// Small spacing (8px) - unit × 2 + static const double sm = 8.0; + + /// Medium spacing (16px) - unit × 4 - DEFAULT + static const double md = 16.0; + + /// Large spacing (24px) - unit × 6 + static const double lg = 24.0; + + /// Extra large spacing (32px) - unit × 8 + static const double xl = 32.0; + + /// Double extra large (48px) - unit × 12 + static const double xxl = 48.0; + + /// Triple extra large (64px) - unit × 16 + static const double xxxl = 64.0; + + // ============================================ + // COMPONENT-SPECIFIC SPACING + // ============================================ + + /// Padding inside cards + static const double cardPadding = md; // 16px + + /// Horizontal button padding + static const double buttonPaddingX = lg; // 24px + + /// Vertical button padding + static const double buttonPaddingY = 12.0; // sm × 1.5 + + /// Padding in input fields + static const double inputPadding = md; // 16px + + /// Standard icon size + static const double iconSize = lg; // 24px + + /// Large icon size + static const double iconSizeLarge = xl; // 32px + + /// Dialog content padding + static const double dialogPadding = lg; // 24px + + /// Standard app bar height + static const double appBarHeight = 56.0; + + /// List item padding + static const double listItemPadding = md; // 16px + + // ============================================ + // PRE-BUILT EDGEINSETS - ALL SIDES + // ============================================ + + /// EdgeInsets.all(4px) + static const EdgeInsets paddingAllXs = EdgeInsets.all(xs); + + /// EdgeInsets.all(8px) + static const EdgeInsets paddingAllSm = EdgeInsets.all(sm); + + /// EdgeInsets.all(16px) + static const EdgeInsets paddingAllMd = EdgeInsets.all(md); + + /// EdgeInsets.all(24px) + static const EdgeInsets paddingAllLg = EdgeInsets.all(lg); + + /// EdgeInsets.all(32px) + static const EdgeInsets paddingAllXl = EdgeInsets.all(xl); + + /// EdgeInsets.all(48px) + static const EdgeInsets paddingAllXxl = EdgeInsets.all(xxl); + + // ============================================ + // PRE-BUILT EDGEINSETS - HORIZONTAL + // ============================================ + + /// EdgeInsets.symmetric(horizontal: 4px) + static const EdgeInsets paddingHorizontalXs = + EdgeInsets.symmetric(horizontal: xs); + + /// EdgeInsets.symmetric(horizontal: 8px) + static const EdgeInsets paddingHorizontalSm = + EdgeInsets.symmetric(horizontal: sm); + + /// EdgeInsets.symmetric(horizontal: 16px) + static const EdgeInsets paddingHorizontalMd = + EdgeInsets.symmetric(horizontal: md); + + /// EdgeInsets.symmetric(horizontal: 24px) + static const EdgeInsets paddingHorizontalLg = + EdgeInsets.symmetric(horizontal: lg); + + /// EdgeInsets.symmetric(horizontal: 32px) + static const EdgeInsets paddingHorizontalXl = + EdgeInsets.symmetric(horizontal: xl); + + // ============================================ + // PRE-BUILT EDGEINSETS - VERTICAL + // ============================================ + + /// EdgeInsets.symmetric(vertical: 4px) + static const EdgeInsets paddingVerticalXs = + EdgeInsets.symmetric(vertical: xs); + + /// EdgeInsets.symmetric(vertical: 8px) + static const EdgeInsets paddingVerticalSm = + EdgeInsets.symmetric(vertical: sm); + + /// EdgeInsets.symmetric(vertical: 16px) + static const EdgeInsets paddingVerticalMd = + EdgeInsets.symmetric(vertical: md); + + /// EdgeInsets.symmetric(vertical: 24px) + static const EdgeInsets paddingVerticalLg = + EdgeInsets.symmetric(vertical: lg); + + /// EdgeInsets.symmetric(vertical: 32px) + static const EdgeInsets paddingVerticalXl = + EdgeInsets.symmetric(vertical: xl); + + // ============================================ + // PRE-BUILT EDGEINSETS - COMPONENT SPECIFIC + // ============================================ + + /// Card padding (16px all sides) + static const EdgeInsets paddingCard = paddingAllMd; + + /// Button padding (horizontal: 24px, vertical: 12px) + static const EdgeInsets paddingButton = + EdgeInsets.symmetric(horizontal: lg, vertical: buttonPaddingY); + + /// List item padding (horizontal: 16px, vertical: 8px) + static const EdgeInsets paddingListItem = + EdgeInsets.symmetric(horizontal: md, vertical: sm); + + /// Dialog padding (24px all sides) + static const EdgeInsets paddingDialog = paddingAllLg; + + // ============================================ + // SPACER WIDGETS - UNIVERSAL (SQUARE) + // ============================================ + + /// SizedBox(width: 4, height: 4) + static const Widget spacerXs = SizedBox(width: xs, height: xs); + + /// SizedBox(width: 8, height: 8) + static const Widget spacerSm = SizedBox(width: sm, height: sm); + + /// SizedBox(width: 16, height: 16) + static const Widget spacerMd = SizedBox(width: md, height: md); + + /// SizedBox(width: 24, height: 24) + static const Widget spacerLg = SizedBox(width: lg, height: lg); + + /// SizedBox(width: 32, height: 32) + static const Widget spacerXl = SizedBox(width: xl, height: xl); + + // ============================================ + // SPACER WIDGETS - VERTICAL + // ============================================ + + /// SizedBox(height: 4) + static const Widget vSpacerXs = SizedBox(height: xs); + + /// SizedBox(height: 8) + static const Widget vSpacerSm = SizedBox(height: sm); + + /// SizedBox(height: 16) + static const Widget vSpacerMd = SizedBox(height: md); + + /// SizedBox(height: 24) + static const Widget vSpacerLg = SizedBox(height: lg); + + /// SizedBox(height: 32) + static const Widget vSpacerXl = SizedBox(height: xl); + + /// SizedBox(height: 48) + static const Widget vSpacerXxl = SizedBox(height: xxl); + + /// SizedBox(height: 64) + static const Widget vSpacerXxxl = SizedBox(height: xxxl); + + // ============================================ + // SPACER WIDGETS - HORIZONTAL + // ============================================ + + /// SizedBox(width: 4) + static const Widget hSpacerXs = SizedBox(width: xs); + + /// SizedBox(width: 8) + static const Widget hSpacerSm = SizedBox(width: sm); + + /// SizedBox(width: 16) + static const Widget hSpacerMd = SizedBox(width: md); + + /// SizedBox(width: 24) + static const Widget hSpacerLg = SizedBox(width: lg); + + /// SizedBox(width: 32) + static const Widget hSpacerXl = SizedBox(width: xl); + + /// SizedBox(width: 48) + static const Widget hSpacerXxl = SizedBox(width: xxl); + + /// SizedBox(width: 64) + static const Widget hSpacerXxxl = SizedBox(width: xxxl); + + // ============================================ + // GAPS FOR ROW/COLUMN SPACING + // ============================================ + + /// Gap for Row/Column (4px) + static const double gapXs = xs; + + /// Gap for Row/Column (8px) + static const double gapSm = sm; + + /// Gap for Row/Column (16px) + static const double gapMd = md; + + /// Gap for Row/Column (24px) + static const double gapLg = lg; + + /// Gap for Row/Column (32px) + static const double gapXl = xl; + + // ============================================ + // RESPONSIVE SCREEN MARGINS + // ============================================ + + /// Screen margin for mobile devices (< 600px) + static const double screenMarginMobile = md; // 16px + + /// Screen margin for tablets (600-1024px) + static const double screenMarginTablet = lg; // 24px + + /// Screen margin for desktop (> 1024px) + static const double screenMarginDesktop = xl; // 32px + + /// Max content width constraint + static const double maxContentWidth = 1200.0; + + /// Compact content width for forms/compact layouts + static const double compactContentWidth = 600.0; + + // ============================================ + // RESPONSIVE PADDING FOR SCREENS + // ============================================ + + /// Screen padding for mobile + static const EdgeInsets screenPaddingMobile = + EdgeInsets.symmetric(horizontal: screenMarginMobile); + + /// Screen padding for tablet + static const EdgeInsets screenPaddingTablet = + EdgeInsets.symmetric(horizontal: screenMarginTablet); + + /// Screen padding for desktop + static const EdgeInsets screenPaddingDesktop = + EdgeInsets.symmetric(horizontal: screenMarginDesktop); + + // ============================================ + // UTILITY METHODS + // ============================================ + + /// Get responsive screen margin based on screen width + static double getScreenMargin(double screenWidth) { + if (screenWidth < 600) { + return screenMarginMobile; + } else if (screenWidth < 1024) { + return screenMarginTablet; + } else { + return screenMarginDesktop; + } + } + + /// Get responsive screen padding based on screen width + static EdgeInsets getScreenPadding(double screenWidth) { + final margin = getScreenMargin(screenWidth); + return EdgeInsets.symmetric(horizontal: margin); + } +} diff --git a/lib/theme/typography_system.dart b/lib/theme/typography_system.dart new file mode 100644 index 0000000..2da630c --- /dev/null +++ b/lib/theme/typography_system.dart @@ -0,0 +1,331 @@ +import 'package:flutter/material.dart'; + +/// Svrnty Typography System +/// Extended text styles and typography utilities beyond Material Design 3 +class AppTypography { + // Prevent instantiation + AppTypography._(); + + // ============================================ + // FONT FAMILIES + // ============================================ + + /// Primary font family (Montserrat) + static const String fontFamilyPrimary = 'Montserrat'; + + /// Monospace font family (IBM Plex Mono) + static const String fontFamilyMono = 'IBMPlexMono'; + + // ============================================ + // FONT WEIGHTS + // ============================================ + + /// Light font weight (300) + static const FontWeight weightLight = FontWeight.w300; + + /// Regular font weight (400) + static const FontWeight weightRegular = FontWeight.w400; + + /// Medium font weight (500) + static const FontWeight weightMedium = FontWeight.w500; + + /// Semi-bold font weight (600) + static const FontWeight weightSemiBold = FontWeight.w600; + + /// Bold font weight (700) + static const FontWeight weightBold = FontWeight.w700; + + // ============================================ + // FONT SIZES + // ============================================ + + /// Display Large font size (57px) + static const double sizeDisplayLarge = 57.0; + + /// Display Medium font size (45px) + static const double sizeDisplayMedium = 45.0; + + /// Display Small font size (36px) + static const double sizeDisplaySmall = 36.0; + + /// Headline Large font size (32px) + static const double sizeHeadlineLarge = 32.0; + + /// Headline Medium font size (28px) + static const double sizeHeadlineMedium = 28.0; + + /// Headline Small font size (24px) + static const double sizeHeadlineSmall = 24.0; + + /// Title Large font size (22px) + static const double sizeTitleLarge = 22.0; + + /// Title Medium font size (16px) + static const double sizeTitleMedium = 16.0; + + /// Title Small font size (14px) + static const double sizeTitleSmall = 14.0; + + /// Body Large font size (16px) + static const double sizeBodyLarge = 16.0; + + /// Body Medium font size (14px) + static const double sizeBodyMedium = 14.0; + + /// Body Small font size (12px) + static const double sizeBodySmall = 12.0; + + /// Label Large font size (14px) + static const double sizeLabelLarge = 14.0; + + /// Label Medium font size (12px) + static const double sizeLabelMedium = 12.0; + + /// Label Small font size (11px) + static const double sizeLabelSmall = 11.0; + + // ============================================ + // LINE HEIGHTS + // ============================================ + + /// Display Large line height (1.12 = 64px) + static const double lineHeightDisplayLarge = 1.12; + + /// Display Medium line height (1.16 = 52px) + static const double lineHeightDisplayMedium = 1.16; + + /// Display Small line height (1.22 = 44px) + static const double lineHeightDisplaySmall = 1.22; + + /// Headline Large line height (1.25 = 40px) + static const double lineHeightHeadlineLarge = 1.25; + + /// Headline Medium line height (1.29 = 36px) + static const double lineHeightHeadlineMedium = 1.29; + + /// Headline Small line height (1.33 = 32px) + static const double lineHeightHeadlineSmall = 1.33; + + /// Title Large line height (1.27 = 28px) + static const double lineHeightTitleLarge = 1.27; + + /// Title Medium line height (1.5 = 24px) + static const double lineHeightTitleMedium = 1.5; + + /// Title Small line height (1.43 = 20px) + static const double lineHeightTitleSmall = 1.43; + + /// Body Large line height (1.5 = 24px) + static const double lineHeightBodyLarge = 1.5; + + /// Body Medium line height (1.43 = 20px) + static const double lineHeightBodyMedium = 1.43; + + /// Body Small line height (1.33 = 16px) + static const double lineHeightBodySmall = 1.33; + + /// Label Large line height (1.43 = 20px) + static const double lineHeightLabelLarge = 1.43; + + /// Label Medium line height (1.33 = 16px) + static const double lineHeightLabelMedium = 1.33; + + /// Label Small line height (1.45 = 16px) + static const double lineHeightLabelSmall = 1.45; + + // ============================================ + // LETTER SPACING + // ============================================ + + /// Display Large letter spacing (-0.5px) + static const double letterSpacingDisplayLarge = -0.5; + + /// Display Medium letter spacing (-0.5px) + static const double letterSpacingDisplayMedium = -0.5; + + /// Display Small letter spacing (-0.25px) + static const double letterSpacingDisplaySmall = -0.25; + + /// Headline Large letter spacing (-0.25px) + static const double letterSpacingHeadlineLarge = -0.25; + + /// Headline Medium letter spacing (0px) + static const double letterSpacingHeadlineMedium = 0.0; + + /// Headline Small letter spacing (0px) + static const double letterSpacingHeadlineSmall = 0.0; + + /// Title Large letter spacing (0px) + static const double letterSpacingTitleLarge = 0.0; + + /// Title Medium letter spacing (0.15px) + static const double letterSpacingTitleMedium = 0.15; + + /// Title Small letter spacing (0.1px) + static const double letterSpacingTitleSmall = 0.1; + + /// Body Large letter spacing (0.5px) + static const double letterSpacingBodyLarge = 0.5; + + /// Body Medium letter spacing (0.25px) + static const double letterSpacingBodyMedium = 0.25; + + /// Body Small letter spacing (0.4px) + static const double letterSpacingBodySmall = 0.4; + + /// Label Large letter spacing (0.1px) + static const double letterSpacingLabelLarge = 0.1; + + /// Label Medium letter spacing (0.5px) + static const double letterSpacingLabelMedium = 0.5; + + /// Label Small letter spacing (0.5px) + static const double letterSpacingLabelSmall = 0.5; + + // ============================================ + // CUSTOM TEXT STYLES + // ============================================ + + /// Monospace small text style + static const TextStyle monoSmall = TextStyle( + fontFamily: fontFamilyMono, + fontSize: sizeBodySmall, + fontWeight: weightRegular, + ); + + /// Monospace medium text style + static const TextStyle monoMedium = TextStyle( + fontFamily: fontFamilyMono, + fontSize: sizeBodyMedium, + fontWeight: weightRegular, + ); + + /// Monospace large text style + static const TextStyle monoLarge = TextStyle( + fontFamily: fontFamilyMono, + fontSize: sizeBodyLarge, + fontWeight: weightRegular, + ); + + /// Monospace bold text style + static const TextStyle monoBold = TextStyle( + fontFamily: fontFamilyMono, + fontSize: sizeBodyMedium, + fontWeight: weightBold, + ); + + /// Code snippet text style + static const TextStyle codeStyle = TextStyle( + fontFamily: fontFamilyMono, + fontSize: sizeBodySmall, + fontWeight: weightRegular, + backgroundColor: Color(0xFFF5F5F5), + ); + + // ============================================ + // TEXT STYLE EXTENSIONS + // ============================================ + + /// Create a text style with color override + static TextStyle withColor( + TextStyle baseStyle, + Color color, + ) { + return baseStyle.copyWith(color: color); + } + + /// Create a text style with size override + static TextStyle withSize( + TextStyle baseStyle, + double fontSize, + ) { + return baseStyle.copyWith(fontSize: fontSize); + } + + /// Create a text style with weight override + static TextStyle withWeight( + TextStyle baseStyle, + FontWeight fontWeight, + ) { + return baseStyle.copyWith(fontWeight: fontWeight); + } + + /// Create a text style with opacity + static TextStyle withOpacity( + TextStyle baseStyle, + double opacity, + ) { + final color = baseStyle.color ?? Colors.black; + return baseStyle.copyWith( + color: color.withOpacity(opacity), + ); + } + + /// Create a text style with letter spacing override + static TextStyle withLetterSpacing( + TextStyle baseStyle, + double letterSpacing, + ) { + return baseStyle.copyWith(letterSpacing: letterSpacing); + } + + /// Create a text style with line height override + static TextStyle withLineHeight( + TextStyle baseStyle, + double height, + ) { + return baseStyle.copyWith(height: height); + } + + /// Create a monospace version of a text style + static TextStyle toMonospace(TextStyle baseStyle) { + return baseStyle.copyWith( + fontFamily: fontFamilyMono, + ); + } + + /// Create an italic version of a text style + static TextStyle toItalic(TextStyle baseStyle) { + return baseStyle.copyWith( + fontStyle: FontStyle.italic, + ); + } + + /// Create a strikethrough version of a text style + static TextStyle withStrikethrough(TextStyle baseStyle) { + return baseStyle.copyWith( + decoration: TextDecoration.lineThrough, + ); + } + + /// Create an underlined version of a text style + static TextStyle withUnderline(TextStyle baseStyle) { + return baseStyle.copyWith( + decoration: TextDecoration.underline, + ); + } + + /// Create a text style with multiple modifications + static TextStyle merge( + TextStyle baseStyle, { + Color? color, + double? fontSize, + FontWeight? fontWeight, + double? letterSpacing, + double? height, + String? fontFamily, + FontStyle? fontStyle, + TextDecoration? decoration, + }) { + return baseStyle.copyWith( + color: color, + fontSize: fontSize, + fontWeight: fontWeight, + letterSpacing: letterSpacing, + height: height, + fontFamily: fontFamily, + fontStyle: fontStyle, + decoration: decoration, + ); + } +}