173 lines
5.3 KiB
Dart
173 lines
5.3 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
|
||
import 'colors.dart';
|
||
|
||
/// -----------------------------------------------------------------------------
|
||
/// Intaleq Design Tokens
|
||
/// -----------------------------------------------------------------------------
|
||
/// A single source of truth for spacing, radii, shadows, durations and the
|
||
/// shared bottom-sheet surface used across the passenger map overlays.
|
||
///
|
||
/// Goal: kill the "magic numbers" scattered across every map widget
|
||
/// (radii of 15/24/25, ad-hoc shadows, handles that appear only sometimes)
|
||
/// so all sheets read as ONE consistent product — the way Uber / Careem / Bolt
|
||
/// feel like a single system rather than a pile of separate screens.
|
||
///
|
||
/// Everything here is additive. Existing widgets keep working untouched; new
|
||
/// and refactored widgets opt in.
|
||
|
||
/// 4pt spacing scale. Use these instead of raw `SizedBox(height: 10)`.
|
||
class AppSpacing {
|
||
AppSpacing._();
|
||
|
||
static const double xs = 4;
|
||
static const double sm = 8;
|
||
static const double md = 12;
|
||
static const double lg = 16;
|
||
static const double xl = 20;
|
||
static const double xxl = 24;
|
||
|
||
// Convenience gaps (const so they can live in const child lists).
|
||
static const Widget gapXs = SizedBox(height: xs, width: xs);
|
||
static const Widget gapSm = SizedBox(height: sm, width: sm);
|
||
static const Widget gapMd = SizedBox(height: md, width: md);
|
||
static const Widget gapLg = SizedBox(height: lg, width: lg);
|
||
static const Widget gapXl = SizedBox(height: xl, width: xl);
|
||
|
||
static const Widget vGapXs = SizedBox(height: xs);
|
||
static const Widget vGapSm = SizedBox(height: sm);
|
||
static const Widget vGapMd = SizedBox(height: md);
|
||
static const Widget vGapLg = SizedBox(height: lg);
|
||
static const Widget vGapXl = SizedBox(height: xl);
|
||
}
|
||
|
||
/// Corner radii. `sheet` is THE radius for every bottom sheet so they all match.
|
||
class AppRadii {
|
||
AppRadii._();
|
||
|
||
static const double sm = 8;
|
||
static const double md = 12;
|
||
static const double lg = 16;
|
||
static const double sheet = 24;
|
||
static const double pill = 999;
|
||
|
||
static const BorderRadius sheetTop = BorderRadius.only(
|
||
topLeft: Radius.circular(sheet),
|
||
topRight: Radius.circular(sheet),
|
||
);
|
||
|
||
static BorderRadius all(double r) => BorderRadius.circular(r);
|
||
}
|
||
|
||
/// Shared elevation. One shadow for sheets, one for cards — no more per-widget
|
||
/// hand-tuned `BoxShadow`s (and no neumorphic accent-color shadows).
|
||
class AppShadows {
|
||
AppShadows._();
|
||
|
||
static List<BoxShadow> get sheet => [
|
||
BoxShadow(
|
||
color: Colors.black.withValues(
|
||
alpha: Get.isDarkMode ? 0.45 : 0.12,
|
||
),
|
||
blurRadius: 24,
|
||
spreadRadius: 1,
|
||
offset: const Offset(0, -4),
|
||
),
|
||
];
|
||
|
||
static List<BoxShadow> get card => [
|
||
BoxShadow(
|
||
color: Colors.black.withValues(
|
||
alpha: Get.isDarkMode ? 0.30 : 0.06,
|
||
),
|
||
blurRadius: 12,
|
||
offset: const Offset(0, 4),
|
||
),
|
||
];
|
||
}
|
||
|
||
/// Motion durations. Keep sheet slide-in/out consistent everywhere.
|
||
class AppDurations {
|
||
AppDurations._();
|
||
|
||
static const Duration fast = Duration(milliseconds: 200);
|
||
static const Duration base = Duration(milliseconds: 300);
|
||
static const Duration slow = Duration(milliseconds: 450);
|
||
}
|
||
|
||
/// Minimum touch target (Material / iOS HIG guidance is 44–48px).
|
||
class AppSizes {
|
||
AppSizes._();
|
||
|
||
static const double minTouch = 48;
|
||
}
|
||
|
||
/// -----------------------------------------------------------------------------
|
||
/// Shared bottom-sheet surface
|
||
/// -----------------------------------------------------------------------------
|
||
/// The rounded-top card + shadow + optional drag handle that every map sheet
|
||
/// draws by hand today. Wrap your content in this and all sheets instantly
|
||
/// share the same radius, shadow, handle and horizontal rhythm.
|
||
///
|
||
/// It does NOT own show/hide animation — each caller keeps its own
|
||
/// AnimatedPositioned / transform logic so behaviour is unchanged.
|
||
class IntaleqSheetSurface extends StatelessWidget {
|
||
const IntaleqSheetSurface({
|
||
super.key,
|
||
required this.child,
|
||
this.padding =
|
||
const EdgeInsets.fromLTRB(AppSpacing.xl, AppSpacing.md, AppSpacing.xl, AppSpacing.lg),
|
||
this.showHandle = true,
|
||
this.color,
|
||
});
|
||
|
||
final Widget child;
|
||
final EdgeInsets padding;
|
||
final bool showHandle;
|
||
final Color? color;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Container(
|
||
width: double.infinity,
|
||
decoration: BoxDecoration(
|
||
color: color ?? AppColor.cardColor,
|
||
borderRadius: AppRadii.sheetTop,
|
||
boxShadow: AppShadows.sheet,
|
||
),
|
||
padding: padding,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
if (showHandle) ...[
|
||
const IntaleqSheetHandle(),
|
||
AppSpacing.vGapMd,
|
||
],
|
||
child,
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// The little grab handle at the top of a sheet. Consistent size everywhere.
|
||
class IntaleqSheetHandle extends StatelessWidget {
|
||
const IntaleqSheetHandle({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Center(
|
||
child: Container(
|
||
width: 40,
|
||
height: 4,
|
||
decoration: BoxDecoration(
|
||
color: AppColor.grayColor.withValues(alpha: 0.35),
|
||
borderRadius: BorderRadius.circular(AppRadii.pill),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|