Files
doormile_milderapp/lib/views/Dashboard/home/accepted_pill.dart
2026-09-09 12:55:23 +05:30

193 lines
9.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:miler/data/service_profile.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
import 'package:miler/views/helpers/constants/design_constants.dart';
import 'package:miler/views/helpers/constants/Font_constant.dart';
import 'package:miler/widget/Bottom_page.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// ACCEPTED PILL — how many stops you have taken, and the way to them
///
/// Home is where the rider *decides*; Bookings is where he *works*. The bridge
/// between the two used to be a "Continue trip" button sitting under the last
/// hub row of a trip — which meant it only existed once that particular trip
/// had nothing left to decide, it moved as the route grew, and it was below the
/// fold on any route longer than about four stops. A rider who had accepted
/// three stops and scrolled back to the top had nothing on screen telling him
/// so, and no way through.
///
/// This says both things in one control, in one fixed p~lace: **how many
/// bookings he is holding**, and one tap to go work them. It is the counterpart
/// to [SelectionBar] — that one is about the stops he is choosing, this one
/// about the stops he has already taken — so they share the floating slot at
/// the foot of the page and never appear together. A selection in progress is
/// the more urgent of the two and wins the slot.
///
/// The count comes from the accepted store, which drops a booking once it is
/// picked, so the number is "waiting for you on Bookings" rather than "accepted
/// at some point today".
/// ─────────────────────────────────────────────────────────────────────────
class AcceptedPill extends StatelessWidget {
/// The tallest this pill gets, excluding the device inset. See
/// [SelectionBar.maxHeight] for why the surface declares its own.
static double get maxHeight => ButtonSizes.compact + 24.h;
/// Accepted bookings still waiting to be worked. Zero renders nothing.
final int count;
/// Jumps to the Bookings tab.
final VoidCallback onTap;
const AcceptedPill({super.key, required this.count, required this.onTap});
@override
Widget build(BuildContext context) {
if (count <= 0) return const SizedBox.shrink();
final profile = ServiceProfile.active;
final radius = BorderRadius.circular(DesignConstants.radiusFull);
// Rides the floating nav bar: it is `Positioned` outside the Scaffold's
// body, so `SafeArea` knows nothing about it and anything pinned to the
// bottom lands underneath. See [BottomPage.bottomInset].
//
// A fixed inset now. This used to follow a `navVisible` notifier up and
// down, because the bar left the screen entirely on scroll. It compacts
// instead of leaving, so there is no longer a state in which the room is
// free — and chasing the 20pt difference re-laid this out every time the
// rider changed scroll direction, for a gap he would never notice.
return Padding(
padding: EdgeInsets.only(bottom: BottomPage.bottomInset(context) + 10.h),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Align(
// Right-aligned and sized to its content: this is an ambient way
// through, not the action the screen is for. A full-width green bar
// here would out-rank the accept the rider is actually on Home to
// make.
alignment: Alignment.centerRight,
child: Semantics(
button: true,
// The noun follows the rider's line of work — "booking" on a
// parcel route, "delivery" on a meal run. See [ServiceProfile].
label: count == 1
? '1 ${profile.jobNoun} ${_verb(profile)}. '
'Go to ${profile.workTabLabel}.'
: '$count ${profile.jobNounPlural} ${_verb(profile)}. '
'Go to ${profile.workTabLabel}.',
// ── Red is for the action, not for the notice ──
//
// This was a solid brand-red slab with a white disc in it, floating
// over the foot of the route. On a page whose one accent moment is
// supposed to be the journey button, it was the largest and
// reddest object on the screen — and it is not an action the screen
// exists for at all. It is a *notice with a way through*: "you are
// holding nine of these, they are over there."
//
// So the surface goes quiet — the same lifted white the selection
// bar uses, which is the app's one "floating over the list"
// material — and the brand is spent on the two things that carry
// the meaning: the count and the arrow out. Same height, same
// wording, a third of the ink.
child: Material(
color: ColorConstants.pureSurface,
borderRadius: radius,
elevation: 0,
child: InkWell(
onTap: onTap,
borderRadius: radius,
child: Container(
// 48 clears the Material touch-target floor without the pill
// reading as a primary CTA.
height: 48.h,
padding: EdgeInsets.fromLTRB(7.w, 0, 10.w, 0),
decoration: BoxDecoration(
color: ColorConstants.pureSurface,
borderRadius: radius,
boxShadow: DesignConstants.shadowFloat,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// The count as a badge, not as a word in a sentence: it
// is the thing the rider is checking from across the
// handlebars, and it changes while the words do not.
Container(
width: 30.w,
height: 30.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: ColorConstants.primary,
shape: BoxShape.circle,
),
child: Text(
'$count',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w800,
height: 1,
letterSpacing: -0.3,
color: ColorConstants.onAccent,
fontFamily: FontConstants.fontFamily,
),
),
),
SizedBox(width: 9.w),
Text(
// ── The word has to match the boundary ──
//
// It said "accepted" on both lines. On a round the
// count is `WorkBoundary.deliveryQueue` — work already
// **collected** and waiting to be delivered — so a
// rider holding twenty-one bags read "21 deliveries
// accepted", which is the wrong verb for the wrong
// rung and understates what he is carrying.
//
// Logistics hands over at acceptance, so there the word
// is right. Same knob the boundary itself reads.
_waiting(profile, count),
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w800,
letterSpacing: -0.1,
color: ColorConstants.slateText,
fontFamily: FontConstants.fontFamily,
),
),
SizedBox(width: 2.w),
Icon(
LucideIcons.chevronRight,
size: 20.sp,
color: ColorConstants.primary,
),
],
),
),
),
),
),
),
),
);
}
/// The verb for the rung this count actually sits on.
///
/// The pill counts `WorkBoundary.deliveryQueue`, and where that boundary sits
/// is a property of the line — [ServiceProfile.handoffAt]. On a round it is
/// **pickup-complete**, so the work is in the rider's hands; on logistics it
/// is acceptance, so "accepted" is exactly right. One knob, read here rather
/// than a line-name check.
static String _verb(ServiceProfile profile) =>
profile.handsOffAtCollection ? 'in hand' : 'accepted';
/// `deliveries in hand` · `bookings accepted`.
static String _waiting(ServiceProfile profile, int count) =>
'${count == 1 ? profile.jobNoun : profile.jobNounPlural} '
'${_verb(profile)}';
}