first commit

This commit is contained in:
2026-08-11 13:16:33 +05:30
parent 18531bf46b
commit c8350563d9
388 changed files with 69656 additions and 0 deletions

172
lib/data/rider_tier.dart Normal file
View File

@@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// RIDER STATUS — the tier ladder the Rewards page is built around
///
/// Modelled on Uber Pro, which is the version of this a rider is most likely to
/// have already used: four named statuses, points earned per completed job over
/// a fixed reward period, and quality standards that gate the higher tiers
/// regardless of volume. Every loyalty-UX source says the same three things
/// belong on the screen before anything else — where you stand, what you have,
/// and exactly how far the next step is — so those are the three this model has
/// to be able to answer.
///
/// ── What is real here and what the business still owns ──
///
/// The *points* are real: the backend already awards them per stop closed
/// inside its ETA, scaled by the distance ridden, and `RewardsController` reads
/// that total. The *standards* are real: on-time and on-route are measured per
/// stop (see `StopCompliance`).
///
/// The **thresholds below are placeholders**, and so is the idea that a tier
/// unlocks anything. Where the bar sits, and what a rider gets for clearing it,
/// are commercial decisions belonging to the hub — not to this file. They live
/// in one place, named, so a real number drops straight in. Nothing in the UI
/// invents a perk: the page states status, progress and standards, and says
/// plainly that the hub sets what each status is worth.
/// ─────────────────────────────────────────────────────────────────────────
enum RiderTier { blue, silver, gold, platinum }
/// Points needed to reach each tier within one reward period.
///
/// PLACEHOLDER — replace with the hub's real ladder. Kept as a single map so
/// the whole ladder can be swapped without touching the page.
const Map<RiderTier, int> kTierThresholds = {
RiderTier.blue: 0,
RiderTier.silver: 150,
RiderTier.gold: 400,
RiderTier.platinum: 800,
};
/// The share of measured stops that must clear a check for the status to hold.
///
/// PLACEHOLDER, same as above. Uber gates its tiers on acceptance and
/// cancellation rates; the equivalents here are the two the hub actually
/// measures — did he make the ETA, did he ride the route.
const int kStandardTarget = 80;
extension RiderTierUi on RiderTier {
String get label => switch (this) {
RiderTier.blue => 'Blue',
RiderTier.silver => 'Silver',
RiderTier.gold => 'Gold',
RiderTier.platinum => 'Platinum',
};
/// The tier above this one, or null at the top.
RiderTier? get next => switch (this) {
RiderTier.blue => RiderTier.silver,
RiderTier.silver => RiderTier.gold,
RiderTier.gold => RiderTier.platinum,
RiderTier.platinum => null,
};
int get threshold => kTierThresholds[this] ?? 0;
IconData get icon => switch (this) {
RiderTier.blue => Icons.pedal_bike_rounded,
RiderTier.silver => Icons.military_tech_rounded,
RiderTier.gold => Icons.workspace_premium_rounded,
RiderTier.platinum => Icons.diamond_rounded,
};
/// Colour of the badge, not of the page. The card behind it stays brand
/// maroon at every tier — a status screen that changes colour scheme as the
/// rider moves up stops looking like the same product.
Color get accent => switch (this) {
RiderTier.blue => const Color(0xFF7FB3D5),
RiderTier.silver => const Color(0xFFC0C6CC),
RiderTier.gold => ColorConstants.rewardAmber,
RiderTier.platinum => const Color(0xFFB9F2FF),
};
}
/// Where a rider stands this period, and how far the next step is.
class TierStanding {
final int points;
final RiderTier tier;
const TierStanding({required this.points, required this.tier});
static TierStanding of(int points) {
var reached = RiderTier.blue;
for (final tier in RiderTier.values) {
if (points >= tier.threshold) reached = tier;
}
return TierStanding(points: points, tier: reached);
}
RiderTier? get nextTier => tier.next;
/// Points still needed for the next status. Null at the top.
int? get pointsToNext {
final next = nextTier;
if (next == null) return null;
final gap = next.threshold - points;
return gap <= 0 ? 0 : gap;
}
/// 0–1 across the current band, for the progress bar.
///
/// Measured from *this* tier's threshold rather than from zero: a rider one
/// point into Gold should see an empty bar to Platinum, not a bar that is
/// already half full because he has a big lifetime number.
double get progressToNext {
final next = nextTier;
if (next == null) return 1;
final floor = tier.threshold;
final span = next.threshold - floor;
if (span <= 0) return 1;
return ((points - floor) / span).clamp(0.0, 1.0);
}
}
/// The reward period: the calendar month.
///
/// Uber runs three-month periods. A month is the right length here because a
/// miler's shift patterns and the hub's rosters are monthly, and because a
/// three-month bar is one a rider cannot move in a day — the whole point of
/// showing progress is that today's work visibly changes it.
class RewardPeriod {
final DateTime start;
final DateTime end;
const RewardPeriod({required this.start, required this.end});
static RewardPeriod current([DateTime? now]) {
final t = now ?? DateTime.now();
return RewardPeriod(
start: DateTime(t.year, t.month, 1),
// Day zero of next month is the last day of this one.
end: DateTime(t.year, t.month + 1, 0),
);
}
static const List<String> _months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
String get label => '${_months[start.month - 1]} ${start.year}';
/// Whole days left, today included. Never negative.
int daysLeft([DateTime? now]) {
final t = now ?? DateTime.now();
final today = DateTime(t.year, t.month, t.day);
final last = DateTime(end.year, end.month, end.day);
final diff = last.difference(today).inDays + 1;
return diff < 0 ? 0 : diff;
}
}