Miler rider app: surface system, visible design language, backend lifecycle
Design system - MilerSurface ladder (canvas → working → raised → floating) with MilerPanel as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1. - Visible vocabulary applied across Home, Deliveries, Activity, Account and the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x), canvas wells for anything that opens, small filled tags for shelf labels, demoted placeholders. Recorded in DESIGN_SYSTEM.md §6. - One icon family: 222 Material glyphs migrated to Lucide; none left outside lib/xpress. - Colour semantics corrected: amber only for what is genuinely owed, brand red reserved for the live stop, disabled primaries go neutral rather than pale. Data and lifecycle - lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart makes admin sequence the single ordering authority; service_day.dart, and stop_area.dart rewritten against live Coimbatore addresses (digit-token stripping, city stoplist, street suffixes, stammer collapse). - countLabel states the load once, in bags. Testing - 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity, sheets and verify, with test/failures/ now gitignored (diff debris). - New pins: home_gutter_test, stop_area_test, plus updated structural bounds. Note: this commit also carries pre-existing working-tree deletions that were present before this work (API_SPEC.md, README.md, demo test fixtures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
@@ -89,13 +90,13 @@ class _FaqPageState extends State<FaqPage> {
|
||||
),
|
||||
_Faq(
|
||||
'I did not hear a new booking arrive.',
|
||||
'Check Account → Alert Sound, play the sound to be sure you can hear it '
|
||||
'Check Account > Alert Sound, play the sound to be sure you can hear it '
|
||||
'over traffic, and make sure the phone is not on silent. Miler also '
|
||||
'needs battery optimisation turned off to alert you reliably.',
|
||||
),
|
||||
_Faq(
|
||||
'How do I change my name, photo or address?',
|
||||
'Account → Edit Profile for your name, photo and email; Account → Saved '
|
||||
'Account > Edit Profile for your name, photo and email; Account > Saved '
|
||||
'Address for your address. Your mobile number is fixed to the one '
|
||||
'your hub registered — contact support to change it.',
|
||||
),
|
||||
@@ -173,7 +174,7 @@ class _FaqPageState extends State<FaqPage> {
|
||||
onChanged: (v) => setState(() => _query = v),
|
||||
style: TextStyle(
|
||||
fontSize: 14.5.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -186,7 +187,7 @@ class _FaqPageState extends State<FaqPage> {
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
prefixIcon: Icon(
|
||||
Icons.search_rounded,
|
||||
LucideIcons.search,
|
||||
size: 20.sp,
|
||||
color: ColorConstants.onSurfaceVariant,
|
||||
),
|
||||
@@ -195,7 +196,7 @@ class _FaqPageState extends State<FaqPage> {
|
||||
: IconButton(
|
||||
tooltip: 'Close',
|
||||
icon: Icon(
|
||||
Icons.close_rounded,
|
||||
LucideIcons.x,
|
||||
size: 18.sp,
|
||||
color: ColorConstants.onSurfaceVariant,
|
||||
),
|
||||
@@ -222,7 +223,7 @@ class _FaqPageState extends State<FaqPage> {
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_off_rounded,
|
||||
LucideIcons.searchX,
|
||||
size: 34.sp,
|
||||
color: ColorConstants.onSurfaceVariant,
|
||||
),
|
||||
@@ -232,7 +233,7 @@ class _FaqPageState extends State<FaqPage> {
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -265,7 +266,7 @@ class _FaqPageState extends State<FaqPage> {
|
||||
'Still stuck?',
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -283,7 +284,7 @@ class _FaqPageState extends State<FaqPage> {
|
||||
SizedBox(height: 14.h),
|
||||
MilerButton(
|
||||
label: 'Contact support',
|
||||
icon: Icons.mail_outline_rounded,
|
||||
icon: LucideIcons.mail,
|
||||
variant: MilerButtonVariant.tonal,
|
||||
height: ButtonSizes.secondary,
|
||||
onPressed: () => openSheet(context, const HelpCenterMessage()),
|
||||
@@ -329,7 +330,7 @@ class _FaqTileState extends State<_FaqTile> {
|
||||
widget.faq.question,
|
||||
style: TextStyle(
|
||||
fontSize: 14.5.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.35,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -342,7 +343,7 @@ class _FaqTileState extends State<_FaqTile> {
|
||||
duration: const Duration(milliseconds: 220),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: Icon(
|
||||
Icons.expand_more_rounded,
|
||||
LucideIcons.chevronDown,
|
||||
size: 22.sp,
|
||||
color: _open
|
||||
? ColorConstants.primary
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -30,6 +31,9 @@ class HelpCenter extends StatelessWidget {
|
||||
/// The hub's support line. One constant, used by the call row.
|
||||
static const String _supportPhone = '+918925545000';
|
||||
|
||||
/// India's emergency number — see the note on `ProfilePage`.
|
||||
static const String _emergencyNumber = '112';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -43,13 +47,13 @@ class HelpCenter extends StatelessWidget {
|
||||
SettingsGroup(
|
||||
rows: [
|
||||
SettingsRow(
|
||||
icon: Icons.mail_outline_rounded,
|
||||
icon: LucideIcons.mail,
|
||||
title: 'Send a message',
|
||||
subtitle: 'We usually reply within a day',
|
||||
onTap: () => openSheet(context, const HelpCenterMessage()),
|
||||
),
|
||||
SettingsRow(
|
||||
icon: Icons.call_outlined,
|
||||
icon: LucideIcons.phone,
|
||||
title: 'Call the hub',
|
||||
subtitle: 'For anything that cannot wait',
|
||||
onTap: () => _dial(context, _supportPhone),
|
||||
@@ -61,7 +65,7 @@ class HelpCenter extends StatelessWidget {
|
||||
SettingsGroup(
|
||||
rows: [
|
||||
SettingsRow(
|
||||
icon: Icons.quiz_outlined,
|
||||
icon: LucideIcons.circleHelp,
|
||||
title: 'Browse the FAQ',
|
||||
subtitle: 'Trips, skips, cash, alerts and payments',
|
||||
onTap: () => openSheet(context, const FaqPage()),
|
||||
@@ -86,7 +90,7 @@ class HelpCenter extends StatelessWidget {
|
||||
'How can we help?',
|
||||
style: TextStyle(
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.4,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -108,19 +112,13 @@ class HelpCenter extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Distinct from support: this is the one for an accident or a threat, and it
|
||||
/// is deliberately the only red-filled thing on the page.
|
||||
Widget _emergency(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => _dial(context, '112'),
|
||||
child: InfoBanner(
|
||||
text:
|
||||
'Emergency — tap to call for help if you are in immediate danger.',
|
||||
icon: Icons.warning_amber_rounded,
|
||||
color: ColorConstants.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
/// Distinct from support: this is the one for an accident or a threat, not
|
||||
/// for a customer who will not pay. Same [EmergencyRow] the Account page
|
||||
/// draws — it used to be a second, differently-worded copy of it.
|
||||
Widget _emergency(BuildContext context) => EmergencyRow(
|
||||
number: _emergencyNumber,
|
||||
onTap: () => _dial(context, _emergencyNumber),
|
||||
);
|
||||
|
||||
/// Says so when the dialer will not open, rather than absorbing the tap.
|
||||
static Future<void> _dial(BuildContext context, String number) async {
|
||||
@@ -222,7 +220,7 @@ class _HelpCenterMessageState extends State<HelpCenterMessage> {
|
||||
'What has happened?',
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -284,7 +282,7 @@ class _HelpCenterMessageState extends State<HelpCenterMessage> {
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -304,7 +302,7 @@ class _HelpCenterMessageState extends State<HelpCenterMessage> {
|
||||
validator: validator,
|
||||
style: TextStyle(
|
||||
fontSize: 14.5.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -11,7 +12,6 @@ import 'package:miler/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:miler/views/helpers/constants/Font_constant.dart';
|
||||
import 'package:miler/views/helpers/widgets/app_widgets.dart';
|
||||
import 'package:miler/views/helpers/widgets/page_transitions.dart';
|
||||
import 'package:miler/views/helpers/widgets/settings_ui.dart';
|
||||
import 'package:miler/views/helpers/constants/design_constants.dart';
|
||||
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
@@ -118,7 +118,7 @@ class _NotificationsPageState extends State<NotificationsPage> {
|
||||
'Clear notifications?',
|
||||
style: TextStyle(
|
||||
fontSize: 17.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -140,7 +140,7 @@ class _NotificationsPageState extends State<NotificationsPage> {
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
color: ColorConstants.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
@@ -151,7 +151,7 @@ class _NotificationsPageState extends State<NotificationsPage> {
|
||||
'Clear',
|
||||
style: TextStyle(
|
||||
color: ColorConstants.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
),
|
||||
@@ -181,7 +181,7 @@ class _NotificationsPageState extends State<NotificationsPage> {
|
||||
'Clear',
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.primary,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -194,7 +194,7 @@ class _NotificationsPageState extends State<NotificationsPage> {
|
||||
? const SkeletonList(count: 5)
|
||||
: _items.isEmpty
|
||||
? const MilerEmptyState(
|
||||
icon: Icons.notifications_none_rounded,
|
||||
icon: LucideIcons.bell,
|
||||
title: 'No notifications yet',
|
||||
message:
|
||||
'Updates about your bookings, your shift and your '
|
||||
@@ -204,17 +204,30 @@ class _NotificationsPageState extends State<NotificationsPage> {
|
||||
onRefresh: _load,
|
||||
color: ColorConstants.primary,
|
||||
backgroundColor: ColorConstants.surfaceContainerLowest,
|
||||
// ── A ruled list, not a stack of cards ──
|
||||
//
|
||||
// Eight shadowed white cards, each holding one sentence behind
|
||||
// an identical icon disc, was the exact idiom Activity and
|
||||
// Account were rebuilt to remove — notifications are the most
|
||||
// list-shaped content in the app and were the last screen
|
||||
// still wearing boxes. Hairlines separate; the tinted glyph
|
||||
// anchors; nothing needs a container to be one item.
|
||||
child: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.fromLTRB(16.w, 14.h, 16.w, 28.h),
|
||||
padding: EdgeInsets.fromLTRB(16.w, 6.h, 16.w, 28.h),
|
||||
itemCount: _items.length,
|
||||
separatorBuilder: (_, _) => SizedBox(height: 12.h),
|
||||
separatorBuilder: (_, _) => Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
indent: 52.w,
|
||||
color: ColorConstants.borderSubtle,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final it = _items[index];
|
||||
return Reveal(
|
||||
key: ValueKey('notif_${it['id'] ?? index}'),
|
||||
delay: staggerDelay(index),
|
||||
child: _NotificationCard(item: it),
|
||||
child: _NotificationRow(item: it),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -224,41 +237,57 @@ class _NotificationsPageState extends State<NotificationsPage> {
|
||||
}
|
||||
}
|
||||
|
||||
/// One notification, in the section's card.
|
||||
/// One notification, as one row of the ruled list.
|
||||
///
|
||||
/// The kind badge is the only colour on the card, and it comes from the token
|
||||
/// set: brand red for work, the money green for earnings, the info blue for
|
||||
/// everything else. It used to be `Colors.green.shade700` on `green.shade50`,
|
||||
/// `Colors.blue.shade700` on `blue.shade50` and a raw `0xFFFEE2E2` — three
|
||||
/// palettes that appear nowhere else in the app.
|
||||
class _NotificationCard extends StatelessWidget {
|
||||
/// The kind glyph is the only colour on the row, and it comes from the token
|
||||
/// set: brand red for work arriving, the app's done-green for work finished,
|
||||
/// the money green for earnings, the info blue for everything else. A
|
||||
/// completed booking used to wear the same truck as a new assignment — one
|
||||
/// glyph for opposite facts — which is what made eight rows read as one row
|
||||
/// eight times.
|
||||
class _NotificationRow extends StatelessWidget {
|
||||
final Map<String, dynamic> item;
|
||||
|
||||
const _NotificationCard({required this.item});
|
||||
const _NotificationRow({required this.item});
|
||||
|
||||
({IconData icon, Color accent}) _kind() {
|
||||
final type = (item['type'] ?? '').toString().toLowerCase();
|
||||
final title = (item['title'] ?? '').toString().toLowerCase();
|
||||
|
||||
// Finished work first — its titles also contain 'booking', so the
|
||||
// completed test must run before the assignment test or it never fires.
|
||||
if (title.contains('completed') ||
|
||||
title.contains('delivered') ||
|
||||
type == 'completed') {
|
||||
return (
|
||||
icon: LucideIcons.circleCheck,
|
||||
accent: ColorConstants.acceptGreen,
|
||||
);
|
||||
}
|
||||
if (type == 'earning' ||
|
||||
title.contains('payment') ||
|
||||
title.contains('payout') ||
|
||||
title.contains('earning')) {
|
||||
return (
|
||||
icon: Icons.account_balance_wallet_outlined,
|
||||
accent: ColorConstants.moneyGreen,
|
||||
);
|
||||
return (icon: LucideIcons.wallet, accent: ColorConstants.moneyGreen);
|
||||
}
|
||||
if (type == 'task' ||
|
||||
title.contains('pickup') ||
|
||||
title.contains('booking') ||
|
||||
title.contains('trip')) {
|
||||
return (
|
||||
icon: Icons.local_shipping_outlined,
|
||||
accent: ColorConstants.primary,
|
||||
);
|
||||
return (icon: LucideIcons.truck, accent: ColorConstants.primary);
|
||||
}
|
||||
return (icon: Icons.info_outline_rounded, accent: ColorConstants.tertiary);
|
||||
return (icon: LucideIcons.info, accent: ColorConstants.tertiary);
|
||||
}
|
||||
|
||||
/// The server's relative stamps arrive ungrammatical — `1 hrs ago`,
|
||||
/// `1 mins ago` — and were printed verbatim. Singular units drop the s;
|
||||
/// everything else passes through untouched.
|
||||
static String tidyTime(String raw) {
|
||||
final t = raw.trim();
|
||||
final m = RegExp(r'^1\s+(hrs|mins|secs|days)\s+ago$').firstMatch(t);
|
||||
if (m == null) return t;
|
||||
final unit = m.group(1)!;
|
||||
return '1 ${unit.substring(0, unit.length - 1)} ago';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -266,12 +295,12 @@ class _NotificationCard extends StatelessWidget {
|
||||
final kind = _kind();
|
||||
final title = (item['title'] ?? 'Miler').toString();
|
||||
final body = (item['body'] ?? '').toString();
|
||||
final time = (item['time'] ?? 'Just now').toString();
|
||||
final time = tidyTime((item['time'] ?? 'Just now').toString());
|
||||
final imageUrl = (item['imageUrl'] ?? '').toString();
|
||||
final imagePath = (item['imagePath'] ?? '').toString();
|
||||
|
||||
return SettingsCard(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -279,13 +308,13 @@ class _NotificationCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 42.w,
|
||||
height: 42.w,
|
||||
width: 38.w,
|
||||
height: 38.w,
|
||||
decoration: BoxDecoration(
|
||||
color: kind.accent.withValues(alpha: 0.10),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(kind.icon, color: kind.accent, size: 21.sp),
|
||||
child: Icon(kind.icon, color: kind.accent, size: 19.sp),
|
||||
),
|
||||
SizedBox(width: 14.w),
|
||||
Expanded(
|
||||
@@ -300,7 +329,7 @@ class _NotificationCard extends StatelessWidget {
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.3,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -312,7 +341,7 @@ class _NotificationCard extends StatelessWidget {
|
||||
time,
|
||||
style: TextStyle(
|
||||
fontSize: 11.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.onSurfaceVariant,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -152,7 +153,7 @@ class _OrderAlertSoundPageState extends State<OrderAlertSoundPage> {
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.notifications_active_rounded,
|
||||
LucideIcons.bellRing,
|
||||
color: ColorConstants.primary,
|
||||
size: 23.sp,
|
||||
),
|
||||
@@ -166,7 +167,7 @@ class _OrderAlertSoundPageState extends State<OrderAlertSoundPage> {
|
||||
_currentLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -197,9 +198,7 @@ class _OrderAlertSoundPageState extends State<OrderAlertSoundPage> {
|
||||
Widget _soundRow(_SoundOption opt) {
|
||||
final bool isSelected = _selected == opt.assetPath;
|
||||
return SettingsRow(
|
||||
icon: isSelected
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.radio_button_unchecked_rounded,
|
||||
icon: isSelected ? LucideIcons.circleCheck : LucideIcons.circle,
|
||||
iconColor: isSelected
|
||||
? ColorConstants.primary
|
||||
: ColorConstants.onSurfaceVariant,
|
||||
@@ -224,7 +223,7 @@ class _OrderAlertSoundPageState extends State<OrderAlertSoundPage> {
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isPlaying ? Icons.graphic_eq_rounded : Icons.play_arrow_rounded,
|
||||
isPlaying ? LucideIcons.audioLines : LucideIcons.play,
|
||||
size: 20.sp,
|
||||
color: isPlaying
|
||||
? ColorConstants.onPrimary
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
@@ -146,7 +147,7 @@ class _ProfileState extends State<Profile> {
|
||||
child: TextButton.icon(
|
||||
onPressed: _pickImage,
|
||||
icon: Icon(
|
||||
Icons.photo_camera_outlined,
|
||||
LucideIcons.camera,
|
||||
size: 16.sp,
|
||||
color: ColorConstants.primary,
|
||||
),
|
||||
@@ -154,7 +155,7 @@ class _ProfileState extends State<Profile> {
|
||||
'Change Photo',
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.primary,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -165,14 +166,14 @@ class _ProfileState extends State<Profile> {
|
||||
_field(
|
||||
label: 'Full name',
|
||||
controller: _nameCtrl,
|
||||
icon: Icons.person_outline_rounded,
|
||||
icon: LucideIcons.user,
|
||||
hint: 'Enter your name',
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
_field(
|
||||
label: 'Mobile number',
|
||||
controller: _contactCtrl,
|
||||
icon: Icons.phone_outlined,
|
||||
icon: LucideIcons.phone,
|
||||
hint: 'Mobile number',
|
||||
readOnly: true,
|
||||
prefix: '+91 ',
|
||||
@@ -181,7 +182,7 @@ class _ProfileState extends State<Profile> {
|
||||
_field(
|
||||
label: 'Email address',
|
||||
controller: _emailCtrl,
|
||||
icon: Icons.mail_outline_rounded,
|
||||
icon: LucideIcons.mail,
|
||||
hint: 'you@example.com',
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
@@ -189,7 +190,7 @@ class _ProfileState extends State<Profile> {
|
||||
_field(
|
||||
label: 'Address',
|
||||
controller: _addressCtrl,
|
||||
icon: Icons.location_on_outlined,
|
||||
icon: LucideIcons.mapPin,
|
||||
hint: 'Your address',
|
||||
maxLines: 2,
|
||||
),
|
||||
@@ -282,7 +283,7 @@ class _ProfileState extends State<Profile> {
|
||||
),
|
||||
child: !hasImage
|
||||
? Icon(
|
||||
Icons.person_rounded,
|
||||
LucideIcons.user,
|
||||
color: ColorConstants.onSurfaceVariant,
|
||||
size: 52.sp,
|
||||
)
|
||||
@@ -303,7 +304,7 @@ class _ProfileState extends State<Profile> {
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.photo_camera_rounded,
|
||||
LucideIcons.camera,
|
||||
color: ColorConstants.onPrimary,
|
||||
size: 15.sp,
|
||||
),
|
||||
@@ -332,7 +333,7 @@ class _ProfileState extends State<Profile> {
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.onSurface,
|
||||
letterSpacing: 0.2,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -346,7 +347,7 @@ class _ProfileState extends State<Profile> {
|
||||
keyboardType: keyboardType,
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: readOnly
|
||||
? ColorConstants.onSurfaceVariant
|
||||
: ColorConstants.onSurface,
|
||||
@@ -357,7 +358,7 @@ class _ProfileState extends State<Profile> {
|
||||
prefixText: prefix,
|
||||
prefixStyle: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -396,7 +397,7 @@ class _ProfileState extends State<Profile> {
|
||||
),
|
||||
suffixIcon: readOnly
|
||||
? Icon(
|
||||
Icons.lock_outline_rounded,
|
||||
LucideIcons.lock,
|
||||
size: 16.sp,
|
||||
color: ColorConstants.secondaryText,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
@@ -176,7 +177,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'${s.tier.label} status',
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.3,
|
||||
color: Colors.white,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -188,7 +189,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'$daysLeft ${daysLeft == 1 ? 'day' : 'days'} left',
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white.withValues(alpha: 0.78),
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -204,7 +205,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
loading ? '—' : '${s.points}',
|
||||
style: TextStyle(
|
||||
fontSize: 30.sp,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -1,
|
||||
height: 1,
|
||||
color: Colors.white,
|
||||
@@ -216,7 +217,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'points',
|
||||
style: TextStyle(
|
||||
fontSize: 11.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.4,
|
||||
color: Colors.white.withValues(alpha: 0.78),
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -243,7 +244,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'${s.nextTier!.label}',
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -283,7 +284,9 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
color: tier.index <= s.tier.index
|
||||
? ColorConstants.primary
|
||||
: ColorConstants.borderStrong,
|
||||
borderRadius: BorderRadius.circular(DesignConstants.radiusFull),
|
||||
borderRadius: BorderRadius.circular(
|
||||
DesignConstants.radiusFull,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6.h),
|
||||
@@ -294,8 +297,8 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
style: TextStyle(
|
||||
fontSize: 10.5.sp,
|
||||
fontWeight: tier == s.tier
|
||||
? FontWeight.w800
|
||||
: FontWeight.w600,
|
||||
? FontWeight.w700
|
||||
: FontWeight.w500,
|
||||
color: tier == s.tier
|
||||
? ColorConstants.primary
|
||||
: ColorConstants.secondaryText,
|
||||
@@ -306,7 +309,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'${tier.threshold}',
|
||||
style: TextStyle(
|
||||
fontSize: 9.5.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -375,7 +378,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
Divider(height: 1, color: ColorConstants.dividerColor),
|
||||
SizedBox(height: 16.h),
|
||||
_meter(
|
||||
icon: Icons.schedule_rounded,
|
||||
icon: LucideIcons.clock,
|
||||
label: 'Reached inside the ETA',
|
||||
hit: s.onTime,
|
||||
total: s.measured,
|
||||
@@ -383,7 +386,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
_meter(
|
||||
icon: Icons.alt_route_rounded,
|
||||
icon: LucideIcons.route,
|
||||
label: 'Rode the assigned route',
|
||||
hit: s.onRoute,
|
||||
total: s.measured,
|
||||
@@ -401,7 +404,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'cleared both checks',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.slateText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -410,7 +413,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
SizedBox(width: 8.w),
|
||||
MilerButton(
|
||||
label: 'See stops',
|
||||
icon: Icons.history_rounded,
|
||||
icon: LucideIcons.history,
|
||||
color: ColorConstants.primary,
|
||||
height: ButtonSizes.compact,
|
||||
expand: false,
|
||||
@@ -430,7 +433,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline_rounded,
|
||||
LucideIcons.info,
|
||||
size: 13.sp,
|
||||
color: ColorConstants.secondaryText,
|
||||
),
|
||||
@@ -444,7 +447,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
style: TextStyle(
|
||||
fontSize: 11.5.sp,
|
||||
height: 1.4,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -477,7 +480,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
child: Column(
|
||||
children: [
|
||||
_rule(
|
||||
icon: Icons.timer_rounded,
|
||||
icon: LucideIcons.timer,
|
||||
title: 'Close a stop before its ETA',
|
||||
body:
|
||||
'Points scale with the distance you rode to reach it. '
|
||||
@@ -485,7 +488,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
),
|
||||
_ruleDivider(),
|
||||
_rule(
|
||||
icon: Icons.alt_route_rounded,
|
||||
icon: LucideIcons.route,
|
||||
title: 'Ride the route your hub assigned',
|
||||
body:
|
||||
'Wandering well past the planned distance breaks the '
|
||||
@@ -493,7 +496,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
),
|
||||
_ruleDivider(),
|
||||
_rule(
|
||||
icon: Icons.verified_rounded,
|
||||
icon: LucideIcons.badgeCheck,
|
||||
title: 'Clear both checks for a compliance point',
|
||||
body:
|
||||
'A stop that makes its ETA and stays on route earns one '
|
||||
@@ -502,7 +505,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
),
|
||||
_ruleDivider(),
|
||||
_rule(
|
||||
icon: Icons.event_repeat_rounded,
|
||||
icon: LucideIcons.calendarSync,
|
||||
title: 'Points reset each month',
|
||||
body:
|
||||
'Your status for the next period is set by the points '
|
||||
@@ -524,7 +527,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.storefront_rounded,
|
||||
LucideIcons.store,
|
||||
size: 14.sp,
|
||||
color: ColorConstants.secondaryText,
|
||||
),
|
||||
@@ -536,7 +539,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
style: TextStyle(
|
||||
fontSize: 11.5.sp,
|
||||
height: 1.45,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -552,7 +555,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 11.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.8,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -562,7 +565,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
TextStyle get _bodyStyle => TextStyle(
|
||||
fontSize: 12.5.sp,
|
||||
height: 1.45,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
);
|
||||
@@ -577,7 +580,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.pending_actions_rounded,
|
||||
LucideIcons.clipboardClock,
|
||||
size: 22.sp,
|
||||
color: ColorConstants.secondaryText,
|
||||
),
|
||||
@@ -624,7 +627,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'$score%',
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.5,
|
||||
height: 1,
|
||||
color: accent,
|
||||
@@ -636,7 +639,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'SCORE',
|
||||
style: TextStyle(
|
||||
fontSize: 8.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.6,
|
||||
height: 1,
|
||||
color: accent,
|
||||
@@ -654,7 +657,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.workspace_premium_rounded,
|
||||
LucideIcons.award,
|
||||
size: 17.sp,
|
||||
color: ColorConstants.acceptGreen,
|
||||
),
|
||||
@@ -667,7 +670,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.3,
|
||||
color: ColorConstants.slateText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -684,7 +687,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
style: TextStyle(
|
||||
fontSize: 11.5.sp,
|
||||
height: 1.35,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.secondaryText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -725,7 +728,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.slateText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -736,7 +739,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
'$hit/$total · $percent%',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.2,
|
||||
color: accent,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
@@ -753,7 +756,9 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(DesignConstants.radiusFull),
|
||||
borderRadius: BorderRadius.circular(
|
||||
DesignConstants.radiusFull,
|
||||
),
|
||||
child: LinearProgressIndicator(
|
||||
value: total == 0 ? 0 : hit / total,
|
||||
minHeight: 8.h,
|
||||
@@ -805,7 +810,7 @@ class _RiderRewardsPageState extends State<RiderRewardsPage> {
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 13.5.sp,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.2,
|
||||
color: ColorConstants.slateText,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -116,7 +117,7 @@ class _SavedAddressPageState extends State<SavedAddressPage> {
|
||||
'Edit',
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.primary,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -172,7 +173,7 @@ class _SavedAddressPageState extends State<SavedAddressPage> {
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.home_outlined,
|
||||
LucideIcons.house,
|
||||
color: ColorConstants.primary,
|
||||
size: 22.sp,
|
||||
),
|
||||
@@ -186,7 +187,7 @@ class _SavedAddressPageState extends State<SavedAddressPage> {
|
||||
'Home address',
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -209,7 +210,7 @@ class _SavedAddressPageState extends State<SavedAddressPage> {
|
||||
SizedBox(height: 14.h),
|
||||
MilerButton(
|
||||
label: hasAddress ? 'Change address' : 'Add your address',
|
||||
icon: hasAddress ? Icons.edit_outlined : Icons.add_rounded,
|
||||
icon: hasAddress ? LucideIcons.pencil : LucideIcons.plus,
|
||||
variant: MilerButtonVariant.tonal,
|
||||
height: ButtonSizes.secondary,
|
||||
onPressed: _startEditing,
|
||||
@@ -232,7 +233,7 @@ class _SavedAddressPageState extends State<SavedAddressPage> {
|
||||
'Home address',
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -248,7 +249,7 @@ class _SavedAddressPageState extends State<SavedAddressPage> {
|
||||
style: TextStyle(
|
||||
fontSize: 15.sp,
|
||||
height: 1.45,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.onSurface,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
@@ -297,7 +298,7 @@ class _SavedAddressPageState extends State<SavedAddressPage> {
|
||||
'Cancel',
|
||||
style: TextStyle(
|
||||
fontSize: 13.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorConstants.onSurfaceVariant,
|
||||
fontFamily: FontConstants.fontFamily,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user