diff --git a/lib/components/premium_route_card.dart b/lib/components/premium_route_card.dart index d9c82c3..da29255 100644 --- a/lib/components/premium_route_card.dart +++ b/lib/components/premium_route_card.dart @@ -64,6 +64,7 @@ class _PremiumRouteCardState extends State final isDark = Theme.of(context).brightness == Brightness.dark; final progressPercentage = (widget.route.progress * 100).toStringAsFixed(0); final isCompleted = widget.route.progress >= 1.0; + final accentColor = isCompleted ? SvrntyColors.statusCompleted : SvrntyColors.crimsonRed; return MouseRegion( onEnter: (_) => _onHoverEnter(), @@ -92,154 +93,98 @@ class _PremiumRouteCardState extends State child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), - color: isDark - ? const Color(0xFF14161A) - : const Color(0xFFFAFAFC), + color: isDark ? const Color(0xFF14161A) : const Color(0xFFFAFAFC), + border: Border( + left: BorderSide(color: accentColor, width: 4), + ), ), + padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // Left accent bar + Header + // 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( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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: 8), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: '${widget.route.deliveredCount}/${widget.route.deliveriesCount}', + style: Theme.of(context).textTheme.bodySmall?.copyWith( fontWeight: FontWeight.w600, - letterSpacing: -0.3, + color: accentColor, ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + ), + TextSpan( + text: ' completed', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: isDark ? Colors.grey[400] : Colors.grey[600], + ), + ), + ], ), - 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, - ), - ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: SvrntyColors.crimsonRed, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + widget.route.deliveriesCount.toString(), + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w600, + ), ), ), ], ), - // Progress section - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Progress percentage text - Text( - '$progressPercentage% progress', - style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: isDark - ? Colors.grey[400] - : Colors.grey[600], - fontSize: 11, - letterSpacing: 0.3, - ), - ), - const SizedBox(height: 8), - // Progress bar with gradient - ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Container( - height: 6, - decoration: BoxDecoration( - color: isDark - ? Colors.grey[800] - : Colors.grey[200], - borderRadius: BorderRadius.circular(4), - ), - child: LinearProgressIndicator( - value: widget.route.progress, - backgroundColor: Colors.transparent, - valueColor: AlwaysStoppedAnimation( - isCompleted - ? SvrntyColors.statusCompleted - : SvrntyColors.crimsonRed, - ), + // Progress + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: SizedBox( + height: 6, + child: LinearProgressIndicator( + value: widget.route.progress, + backgroundColor: isDark ? Colors.grey[800] : Colors.grey[200], + valueColor: AlwaysStoppedAnimation(accentColor), ), ), - ], - ), + ), + ], ), ], ), diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..5493696 --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,368 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; +import 'app_localizations_fr.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations of(BuildContext context) { + return Localizations.of(context, AppLocalizations)!; + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en'), + Locale('fr'), + ]; + + /// No description provided for @appTitle. + /// + /// In en, this message translates to: + /// **'Plan B Logistics'** + String get appTitle; + + /// No description provided for @appDescription. + /// + /// In en, this message translates to: + /// **'Delivery Management System'** + String get appDescription; + + /// No description provided for @loginWithKeycloak. + /// + /// In en, this message translates to: + /// **'Login with Keycloak'** + String get loginWithKeycloak; + + /// No description provided for @deliveryRoutes. + /// + /// In en, this message translates to: + /// **'Delivery Routes'** + String get deliveryRoutes; + + /// No description provided for @routes. + /// + /// In en, this message translates to: + /// **'Routes'** + String get routes; + + /// No description provided for @deliveries. + /// + /// In en, this message translates to: + /// **'Deliveries'** + String get deliveries; + + /// No description provided for @settings. + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// No description provided for @profile. + /// + /// In en, this message translates to: + /// **'Profile'** + String get profile; + + /// No description provided for @logout. + /// + /// In en, this message translates to: + /// **'Logout'** + String get logout; + + /// No description provided for @completed. + /// + /// In en, this message translates to: + /// **'Completed'** + String get completed; + + /// No description provided for @pending. + /// + /// In en, this message translates to: + /// **'Pending'** + String get pending; + + /// No description provided for @todo. + /// + /// In en, this message translates to: + /// **'To Do'** + String get todo; + + /// No description provided for @delivered. + /// + /// In en, this message translates to: + /// **'Delivered'** + String get delivered; + + /// No description provided for @newCustomer. + /// + /// In en, this message translates to: + /// **'New Customer'** + String get newCustomer; + + /// No description provided for @items. + /// + /// In en, this message translates to: + /// **'{count} items'** + String items(int count); + + /// No description provided for @moneyCurrency. + /// + /// In en, this message translates to: + /// **'{amount} MAD'** + String moneyCurrency(double amount); + + /// No description provided for @call. + /// + /// In en, this message translates to: + /// **'Call'** + String get call; + + /// No description provided for @map. + /// + /// In en, this message translates to: + /// **'Map'** + String get map; + + /// No description provided for @more. + /// + /// In en, this message translates to: + /// **'More'** + String get more; + + /// No description provided for @markAsCompleted. + /// + /// In en, this message translates to: + /// **'Mark as Completed'** + String get markAsCompleted; + + /// No description provided for @markAsUncompleted. + /// + /// In en, this message translates to: + /// **'Mark as Uncompleted'** + String get markAsUncompleted; + + /// No description provided for @uploadPhoto. + /// + /// In en, this message translates to: + /// **'Upload Photo'** + String get uploadPhoto; + + /// No description provided for @viewDetails. + /// + /// In en, this message translates to: + /// **'View Details'** + String get viewDetails; + + /// No description provided for @deliverySuccessful. + /// + /// In en, this message translates to: + /// **'Delivery marked as completed'** + String get deliverySuccessful; + + /// No description provided for @deliveryFailed. + /// + /// In en, this message translates to: + /// **'Failed to mark delivery'** + String get deliveryFailed; + + /// No description provided for @noDeliveries. + /// + /// In en, this message translates to: + /// **'No deliveries'** + String get noDeliveries; + + /// No description provided for @noRoutes. + /// + /// In en, this message translates to: + /// **'No routes available'** + String get noRoutes; + + /// No description provided for @error. + /// + /// In en, this message translates to: + /// **'Error: {message}'** + String error(String message); + + /// No description provided for @retry. + /// + /// In en, this message translates to: + /// **'Retry'** + String get retry; + + /// No description provided for @authenticationRequired. + /// + /// In en, this message translates to: + /// **'Authentication required'** + String get authenticationRequired; + + /// No description provided for @phoneCall. + /// + /// In en, this message translates to: + /// **'Call customer'** + String get phoneCall; + + /// No description provided for @navigateToAddress. + /// + /// In en, this message translates to: + /// **'Show on map'** + String get navigateToAddress; + + /// No description provided for @language. + /// + /// In en, this message translates to: + /// **'Language'** + String get language; + + /// No description provided for @english. + /// + /// In en, this message translates to: + /// **'English'** + String get english; + + /// No description provided for @french. + /// + /// In en, this message translates to: + /// **'French'** + String get french; + + /// No description provided for @appVersion. + /// + /// In en, this message translates to: + /// **'App Version'** + String get appVersion; + + /// No description provided for @about. + /// + /// In en, this message translates to: + /// **'About'** + String get about; + + /// No description provided for @fullName. + /// + /// In en, this message translates to: + /// **'{firstName} {lastName}'** + String fullName(String firstName, String lastName); + + /// No description provided for @completedDeliveries. + /// + /// In en, this message translates to: + /// **'{completed}/{total} completed'** + String completedDeliveries(int completed, int total); +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en', 'fr'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + case 'fr': + return AppLocalizationsFr(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..5ec892f --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,137 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get appTitle => 'Plan B Logistics'; + + @override + String get appDescription => 'Delivery Management System'; + + @override + String get loginWithKeycloak => 'Login with Keycloak'; + + @override + String get deliveryRoutes => 'Delivery Routes'; + + @override + String get routes => 'Routes'; + + @override + String get deliveries => 'Deliveries'; + + @override + String get settings => 'Settings'; + + @override + String get profile => 'Profile'; + + @override + String get logout => 'Logout'; + + @override + String get completed => 'Completed'; + + @override + String get pending => 'Pending'; + + @override + String get todo => 'To Do'; + + @override + String get delivered => 'Delivered'; + + @override + String get newCustomer => 'New Customer'; + + @override + String items(int count) { + return '$count items'; + } + + @override + String moneyCurrency(double amount) { + return '$amount MAD'; + } + + @override + String get call => 'Call'; + + @override + String get map => 'Map'; + + @override + String get more => 'More'; + + @override + String get markAsCompleted => 'Mark as Completed'; + + @override + String get markAsUncompleted => 'Mark as Uncompleted'; + + @override + String get uploadPhoto => 'Upload Photo'; + + @override + String get viewDetails => 'View Details'; + + @override + String get deliverySuccessful => 'Delivery marked as completed'; + + @override + String get deliveryFailed => 'Failed to mark delivery'; + + @override + String get noDeliveries => 'No deliveries'; + + @override + String get noRoutes => 'No routes available'; + + @override + String error(String message) { + return 'Error: $message'; + } + + @override + String get retry => 'Retry'; + + @override + String get authenticationRequired => 'Authentication required'; + + @override + String get phoneCall => 'Call customer'; + + @override + String get navigateToAddress => 'Show on map'; + + @override + String get language => 'Language'; + + @override + String get english => 'English'; + + @override + String get french => 'French'; + + @override + String get appVersion => 'App Version'; + + @override + String get about => 'About'; + + @override + String fullName(String firstName, String lastName) { + return '$firstName $lastName'; + } + + @override + String completedDeliveries(int completed, int total) { + return '$completed/$total completed'; + } +} diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart new file mode 100644 index 0000000..5d5b701 --- /dev/null +++ b/lib/l10n/app_localizations_fr.dart @@ -0,0 +1,137 @@ +// 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'; + } +}