Initial commit: Plan B Logistics Flutter app with dark mode and responsive design
Implements complete refactor of Ionic Angular logistics app to Flutter/Dart with: - Svrnty dark mode console theme (Material Design 3) - Responsive layouts (mobile, tablet, desktop) following FRONTEND standards - CQRS API integration with Result<T> error handling - OAuth2/OIDC authentication support (mocked for initial testing) - Delivery route and delivery management features - Multi-language support (EN/FR) with i18n - Native integrations (camera, phone calls, maps) - Strict typing throughout codebase - Mock data for UI testing without backend Follows all FRONTEND style guides, design patterns, and conventions. App is running in dark mode and fully responsive across all device sizes. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum DeviceType {
|
||||
mobile,
|
||||
tablet,
|
||||
desktop,
|
||||
}
|
||||
|
||||
class Breakpoints {
|
||||
Breakpoints._();
|
||||
|
||||
static const double mobile = 600;
|
||||
static const double tablet = 1024;
|
||||
static const double desktop = 1024;
|
||||
|
||||
static const double mobileSmall = 360;
|
||||
static const double mobileLarge = 480;
|
||||
static const double tabletSmall = 600;
|
||||
static const double tabletLarge = 840;
|
||||
static const double desktopSmall = 1024;
|
||||
static const double desktopMedium = 1440;
|
||||
static const double desktopLarge = 1920;
|
||||
static const double desktopUltra = 2560;
|
||||
|
||||
static DeviceType getDeviceType(BuildContext context) {
|
||||
final double width = MediaQuery.of(context).size.width;
|
||||
return getDeviceTypeFromWidth(width);
|
||||
}
|
||||
|
||||
static DeviceType getDeviceTypeFromWidth(double width) {
|
||||
if (width < mobile) {
|
||||
return DeviceType.mobile;
|
||||
} else if (width < desktop) {
|
||||
return DeviceType.tablet;
|
||||
} else {
|
||||
return DeviceType.desktop;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isMobile(BuildContext context) {
|
||||
return getDeviceType(context) == DeviceType.mobile;
|
||||
}
|
||||
|
||||
static bool isTablet(BuildContext context) {
|
||||
return getDeviceType(context) == DeviceType.tablet;
|
||||
}
|
||||
|
||||
static bool isDesktop(BuildContext context) {
|
||||
return getDeviceType(context) == DeviceType.desktop;
|
||||
}
|
||||
|
||||
static bool isTabletOrLarger(BuildContext context) {
|
||||
final DeviceType type = getDeviceType(context);
|
||||
return type == DeviceType.tablet || type == DeviceType.desktop;
|
||||
}
|
||||
|
||||
static bool isMobileOrTablet(BuildContext context) {
|
||||
final DeviceType type = getDeviceType(context);
|
||||
return type == DeviceType.mobile || type == DeviceType.tablet;
|
||||
}
|
||||
|
||||
static T adaptive<T>({
|
||||
required BuildContext context,
|
||||
required T mobile,
|
||||
T? tablet,
|
||||
T? desktop,
|
||||
}) {
|
||||
final DeviceType deviceType = getDeviceType(context);
|
||||
|
||||
switch (deviceType) {
|
||||
case DeviceType.mobile:
|
||||
return mobile;
|
||||
case DeviceType.tablet:
|
||||
return tablet ?? mobile;
|
||||
case DeviceType.desktop:
|
||||
return desktop ?? tablet ?? mobile;
|
||||
}
|
||||
}
|
||||
|
||||
static int getGridColumns(BuildContext context) {
|
||||
final double width = MediaQuery.of(context).size.width;
|
||||
|
||||
if (width < mobileSmall) {
|
||||
return 1;
|
||||
} else if (width < mobileLarge) {
|
||||
return 2;
|
||||
} else if (width < tabletSmall) {
|
||||
return 3;
|
||||
} else if (width < tabletLarge) {
|
||||
return 4;
|
||||
} else if (width < desktopSmall) {
|
||||
return 6;
|
||||
} else if (width < desktopMedium) {
|
||||
return 8;
|
||||
} else {
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
|
||||
static EdgeInsets getAdaptivePadding(BuildContext context) {
|
||||
return adaptive<EdgeInsets>(
|
||||
context: context,
|
||||
mobile: const EdgeInsets.all(16),
|
||||
tablet: const EdgeInsets.all(24),
|
||||
desktop: const EdgeInsets.all(32),
|
||||
);
|
||||
}
|
||||
|
||||
static EdgeInsets getHorizontalPadding(BuildContext context) {
|
||||
return adaptive<EdgeInsets>(
|
||||
context: context,
|
||||
mobile: const EdgeInsets.symmetric(horizontal: 16),
|
||||
tablet: const EdgeInsets.symmetric(horizontal: 32),
|
||||
desktop: const EdgeInsets.symmetric(horizontal: 48),
|
||||
);
|
||||
}
|
||||
|
||||
static double getSpacing(BuildContext context) {
|
||||
return adaptive<double>(
|
||||
context: context,
|
||||
mobile: 8,
|
||||
tablet: 12,
|
||||
desktop: 16,
|
||||
);
|
||||
}
|
||||
|
||||
static double getFontScale(BuildContext context) {
|
||||
return adaptive<double>(
|
||||
context: context,
|
||||
mobile: 1.0,
|
||||
tablet: 1.1,
|
||||
desktop: 1.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension ResponsiveContext on BuildContext {
|
||||
DeviceType get deviceType => Breakpoints.getDeviceType(this);
|
||||
|
||||
bool get isMobile => Breakpoints.isMobile(this);
|
||||
|
||||
bool get isTablet => Breakpoints.isTablet(this);
|
||||
|
||||
bool get isDesktop => Breakpoints.isDesktop(this);
|
||||
|
||||
bool get isTabletOrLarger => Breakpoints.isTabletOrLarger(this);
|
||||
|
||||
bool get isMobileOrTablet => Breakpoints.isMobileOrTablet(this);
|
||||
|
||||
double get screenWidth => MediaQuery.of(this).size.width;
|
||||
|
||||
double get screenHeight => MediaQuery.of(this).size.height;
|
||||
|
||||
T adaptive<T>({
|
||||
required T mobile,
|
||||
T? tablet,
|
||||
T? desktop,
|
||||
}) {
|
||||
return Breakpoints.adaptive<T>(
|
||||
context: this,
|
||||
mobile: mobile,
|
||||
tablet: tablet,
|
||||
desktop: desktop,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'breakpoints.dart';
|
||||
|
||||
class ResponsiveSize {
|
||||
ResponsiveSize._();
|
||||
|
||||
static double widthPercent(BuildContext context, double percent) {
|
||||
assert(percent >= 0 && percent <= 100, 'Percent must be between 0-100');
|
||||
return MediaQuery.of(context).size.width * (percent / 100);
|
||||
}
|
||||
|
||||
static double heightPercent(BuildContext context, double percent) {
|
||||
assert(percent >= 0 && percent <= 100, 'Percent must be between 0-100');
|
||||
return MediaQuery.of(context).size.height * (percent / 100);
|
||||
}
|
||||
|
||||
static double fontSize(BuildContext context, double baseSize) {
|
||||
final double scale = Breakpoints.getFontScale(context);
|
||||
final double screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
double widthScale = 1.0;
|
||||
if (screenWidth < 360) {
|
||||
widthScale = 0.9;
|
||||
} else if (screenWidth > 1920) {
|
||||
widthScale = 1.1;
|
||||
}
|
||||
|
||||
return baseSize * scale * widthScale;
|
||||
}
|
||||
|
||||
static double getMinTapSize(BuildContext context) {
|
||||
final TargetPlatform platform = Theme.of(context).platform;
|
||||
|
||||
switch (platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.android:
|
||||
return 48.0;
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.windows:
|
||||
case TargetPlatform.linux:
|
||||
return 36.0;
|
||||
default:
|
||||
return 44.0;
|
||||
}
|
||||
}
|
||||
|
||||
static double iconSize(BuildContext context, {double baseSize = 24}) {
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: baseSize,
|
||||
tablet: baseSize * 1.2,
|
||||
desktop: baseSize,
|
||||
);
|
||||
}
|
||||
|
||||
static EdgeInsets buttonPadding(BuildContext context) {
|
||||
return Breakpoints.adaptive<EdgeInsets>(
|
||||
context: context,
|
||||
mobile: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
tablet: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
desktop: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
);
|
||||
}
|
||||
|
||||
static double dialogWidth(BuildContext context) {
|
||||
final double screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: screenWidth * 0.9,
|
||||
tablet: screenWidth * 0.7,
|
||||
desktop: 500,
|
||||
);
|
||||
}
|
||||
|
||||
static double dialogMaxHeight(BuildContext context) {
|
||||
final double screenHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: screenHeight * 0.8,
|
||||
tablet: screenHeight * 0.75,
|
||||
desktop: 720,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ResponsiveSpacing {
|
||||
ResponsiveSpacing._();
|
||||
|
||||
static double xs(BuildContext context) {
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: 4,
|
||||
tablet: 6,
|
||||
desktop: 8,
|
||||
);
|
||||
}
|
||||
|
||||
static double sm(BuildContext context) {
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: 8,
|
||||
tablet: 10,
|
||||
desktop: 12,
|
||||
);
|
||||
}
|
||||
|
||||
static double md(BuildContext context) {
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: 16,
|
||||
tablet: 20,
|
||||
desktop: 24,
|
||||
);
|
||||
}
|
||||
|
||||
static double lg(BuildContext context) {
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: 24,
|
||||
tablet: 32,
|
||||
desktop: 40,
|
||||
);
|
||||
}
|
||||
|
||||
static double xl(BuildContext context) {
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: 32,
|
||||
tablet: 48,
|
||||
desktop: 64,
|
||||
);
|
||||
}
|
||||
|
||||
static Widget verticalGap(BuildContext context, {double? size}) {
|
||||
return SizedBox(height: size ?? md(context));
|
||||
}
|
||||
|
||||
static Widget horizontalGap(BuildContext context, {double? size}) {
|
||||
return SizedBox(width: size ?? md(context));
|
||||
}
|
||||
}
|
||||
|
||||
class ResponsiveLayout {
|
||||
ResponsiveLayout._();
|
||||
|
||||
static int formColumns(BuildContext context) {
|
||||
return Breakpoints.adaptive<int>(
|
||||
context: context,
|
||||
mobile: 1,
|
||||
tablet: 2,
|
||||
desktop: 2,
|
||||
);
|
||||
}
|
||||
|
||||
static int gridCrossAxisCount(BuildContext context, {int? maxColumns}) {
|
||||
final int columns = Breakpoints.getGridColumns(context);
|
||||
return maxColumns != null ? columns.clamp(1, maxColumns) : columns;
|
||||
}
|
||||
|
||||
static double gridAspectRatio(BuildContext context) {
|
||||
return Breakpoints.adaptive<double>(
|
||||
context: context,
|
||||
mobile: 1.0,
|
||||
tablet: 1.2,
|
||||
desktop: 1.5,
|
||||
);
|
||||
}
|
||||
|
||||
static bool useSingleColumn(BuildContext context) {
|
||||
return Breakpoints.isMobile(context);
|
||||
}
|
||||
|
||||
static bool useDualPane(BuildContext context) {
|
||||
return Breakpoints.isTabletOrLarger(context);
|
||||
}
|
||||
|
||||
static int getFlex(BuildContext context, {
|
||||
int mobileFlex = 1,
|
||||
int? tabletFlex,
|
||||
int? desktopFlex,
|
||||
}) {
|
||||
return Breakpoints.adaptive<int>(
|
||||
context: context,
|
||||
mobile: mobileFlex,
|
||||
tablet: tabletFlex,
|
||||
desktop: desktopFlex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ResponsiveVisibility {
|
||||
ResponsiveVisibility._();
|
||||
|
||||
static bool showOnMobile(BuildContext context) {
|
||||
return Breakpoints.isMobile(context);
|
||||
}
|
||||
|
||||
static bool showOnTablet(BuildContext context) {
|
||||
return Breakpoints.isTablet(context);
|
||||
}
|
||||
|
||||
static bool showOnDesktop(BuildContext context) {
|
||||
return Breakpoints.isDesktop(context);
|
||||
}
|
||||
|
||||
static bool hideOnMobile(BuildContext context) {
|
||||
return !Breakpoints.isMobile(context);
|
||||
}
|
||||
|
||||
static bool hideOnTablet(BuildContext context) {
|
||||
return !Breakpoints.isTablet(context);
|
||||
}
|
||||
|
||||
static bool hideOnDesktop(BuildContext context) {
|
||||
return !Breakpoints.isDesktop(context);
|
||||
}
|
||||
|
||||
static bool showOnTabletUp(BuildContext context) {
|
||||
return Breakpoints.isTabletOrLarger(context);
|
||||
}
|
||||
|
||||
static bool showOnMobileTablet(BuildContext context) {
|
||||
return Breakpoints.isMobileOrTablet(context);
|
||||
}
|
||||
}
|
||||
|
||||
extension ResponsiveSizeExtension on num {
|
||||
double wp(BuildContext context) {
|
||||
return ResponsiveSize.widthPercent(context, toDouble());
|
||||
}
|
||||
|
||||
double hp(BuildContext context) {
|
||||
return ResponsiveSize.heightPercent(context, toDouble());
|
||||
}
|
||||
|
||||
double rfs(BuildContext context) {
|
||||
return ResponsiveSize.fontSize(context, toDouble());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user