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 <noreply@anthropic.com>
This commit is contained in:
Jean-Philippe Brule 2025-11-15 14:41:32 -05:00
parent ccb817e3c6
commit 3f31a509e0
23 changed files with 3519 additions and 791 deletions

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>logFormatVersion</key>
<integer>11</integer>
<key>logs</key>
<dict/>
</dict>
</plist>

View File

@ -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<Delivery> deliveries;
final Delivery? selectedDelivery;
final ValueChanged<Delivery?>? onDeliverySelected;
const DarkModeMapComponent({
super.key,
required this.deliveries,
this.selectedDelivery,
this.onDeliverySelected,
});
@override
State<DarkModeMapComponent> createState() => _DarkModeMapComponentState();
}
class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
GoogleNavigationViewController? _navigationController;
bool _isNavigating = false;
LatLng? _destinationLocation;
@override
void initState() {
super.initState();
_initializeNavigation();
}
Future<void> _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<void> _navigateToLocation(LatLng location) async {
if (_navigationController == null) return;
try {
await _navigationController!.animateCamera(
CameraUpdate.newLatLngZoom(location, 15),
);
} catch (e) {
debugPrint('Camera navigation error: $e');
}
}
Future<void> _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<void> _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<void> _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,
),
),
],
),
),
),
),
);
}
}

View File

@ -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<DeliveryListItem> createState() => _DeliveryListItemState();
}
class _DeliveryListItemState extends State<DeliveryListItem>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _slideAnimation;
late Animation<double> _fadeAnimation;
late Animation<double> _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<double>(begin: 20, end: 0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
_scaleAnimation = Tween<double>(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,
),
),
),
),
],
),
),
),
),
),
),
);
}
}

View File

@ -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<PremiumRouteCard> createState() => _PremiumRouteCardState();
}
class _PremiumRouteCardState extends State<PremiumRouteCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _shadowAnimation;
bool _isHovered = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: AppAnimations.durationFast,
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.02).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
_shadowAnimation = Tween<double>(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<Color>(
isCompleted
? SvrntyColors.statusCompleted
: SvrntyColors.crimsonRed,
),
),
),
),
],
),
),
],
),
),
),
),
),
);
}
}

View File

@ -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, youll need to edit this
/// file.
///
/// First, open your projects ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// projects 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<AppLocalizations>(context, AppLocalizations)!;
}
static const LocalizationsDelegate<AppLocalizations> 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<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
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<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) =>
<String>['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.',
);
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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<DeliveriesPage> {
.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,
);
},
),

View File

@ -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,
),
),
],
),
),
),
);
},
);
}
}

View File

@ -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<ExtendedColor> get extendedColors => [

View File

@ -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,
}

View File

@ -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);
}

159
lib/theme/color_system.dart Normal file
View File

@ -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);
}

View File

@ -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,
);
}
}

View File

@ -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<Color> colors,
required Alignment begin,
required Alignment end,
List<double>? stops,
}) {
return LinearGradient(
begin: begin,
end: end,
colors: colors,
stops: stops,
);
}
}

View File

@ -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<BoxShadow> shadowNoneLight = [];
/// Minimal shadow for light theme
static const List<BoxShadow> shadowXsLight = [
BoxShadow(
color: _shadowColorLight,
blurRadius: 1,
offset: Offset(0, 1),
),
];
/// Small shadow for light theme (default for cards)
static const List<BoxShadow> shadowSmLight = [
BoxShadow(
color: _shadowColorLight,
blurRadius: 2,
offset: Offset(0, 1),
),
];
/// Medium shadow for light theme (hover states)
static const List<BoxShadow> shadowMdLight = [
BoxShadow(
color: _shadowColorLight,
blurRadius: 4,
offset: Offset(0, 2),
),
];
/// Large shadow for light theme
static const List<BoxShadow> shadowLgLight = [
BoxShadow(
color: _shadowColorLight,
blurRadius: 8,
offset: Offset(0, 4),
),
];
/// Extra large shadow for light theme (dialogs)
static const List<BoxShadow> 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<BoxShadow> shadowNoneDark = [];
/// Minimal shadow for dark theme
static const List<BoxShadow> shadowXsDark = [
BoxShadow(
color: _shadowColorDark,
blurRadius: 1,
offset: Offset(0, 1),
),
];
/// Small shadow for dark theme (default for cards)
static const List<BoxShadow> shadowSmDark = [
BoxShadow(
color: _shadowColorDark,
blurRadius: 2,
offset: Offset(0, 1),
),
];
/// Medium shadow for dark theme (hover states)
static const List<BoxShadow> shadowMdDark = [
BoxShadow(
color: _shadowColorDark,
blurRadius: 4,
offset: Offset(0, 2),
),
];
/// Large shadow for dark theme
static const List<BoxShadow> shadowLgDark = [
BoxShadow(
color: _shadowColorDark,
blurRadius: 8,
offset: Offset(0, 4),
),
];
/// Extra large shadow for dark theme (dialogs)
static const List<BoxShadow> shadowXlDark = [
BoxShadow(
color: _shadowColorDark,
blurRadius: 16,
offset: Offset(0, 8),
),
];
// ============================================
// SHADOW UTILITY METHODS
// ============================================
/// Get shadow list based on brightness and elevation level
static List<BoxShadow> 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;
}

236
lib/theme/size_system.dart Normal file
View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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,
);
}
}