import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../core/formatters.dart'; import '../core/theme.dart'; import '../models/shift.dart'; import '../state/availability_controller.dart'; import '../state/compliance_controller.dart'; import '../state/profile_controller.dart'; import '../state/shifts_controller.dart'; import '../widgets/collapsing_header.dart'; import '../widgets/common.dart'; import '../widgets/shift_card.dart'; import '../widgets/krow_wordmark.dart'; class TodayScreen extends ConsumerWidget { const TodayScreen({super.key}); /// Brand line (40) + gap + identity row (44) + gap + stats, plus 6/18 /// padding. static const _expandedHeader = 188.0; String _greeting() { final h = DateTime.now().hour; if (h < 12) return 'Good morning'; if (h < 18) return 'Good afternoon'; return 'Good evening'; } @override Widget build(BuildContext context, WidgetRef ref) { final worker = ref.watch(workerStatsProvider); final next = ref.watch(nextShiftProvider); final open = ref.watch(openShiftsProvider); final scheduled = ref.watch(scheduledEarningsProvider); final hours = ref.watch(hoursLoggedProvider); final daysSet = ref.watch(daysSetProvider); final compliant = ref.watch(isCompliantProvider); final (done, total) = ref.watch(complianceProgressProvider); return Scaffold( body: CustomScrollView( slivers: [ KrowSliverHeader( expandedHeight: _expandedHeader, collapsedHeight: 60, builder: (context, t) => HeaderCrossFade( t: t, expandedHeight: _expandedHeader, expanded: Padding( padding: const EdgeInsets.fromLTRB(20, 6, 20, 18), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ HeaderBrandLine( trailing: HeaderIconButton( icon: LucideIcons.messageCircle, tooltip: 'Support', onPressed: () => context.push('/support'), ), ), const SizedBox(height: 14), Row( children: [ _Avatar(worker.initials), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _greeting().toUpperCase(), style: TextStyle( fontSize: 9.5, letterSpacing: 1.6, fontWeight: FontWeight.w600, color: Colors.white.withValues(alpha: 0.5), ), ), const SizedBox(height: 2), Text( worker.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w700, letterSpacing: -0.7, color: Colors.white, ), ), ], ), ), ], ), const SizedBox(height: 16), Row( children: [ // Flexible, not free-sized: on a 320px screen the two // captions plus the rule are wider than the header. Flexible( child: _HeaderStat( Fmt.money(scheduled), 'This period', ), ), Container( width: 1, height: 34, margin: const EdgeInsets.symmetric(horizontal: 18), color: Colors.white24, ), Flexible( child: _HeaderStat( Fmt.duration(hours), 'Hours logged', ), ), ], ), ], ), ), // Once collapsed, keep who you are and the figure that matters, // plus the one control that was in the open state. collapsed: Padding( padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), child: Row( children: [ _Avatar(worker.initials, size: 32), const SizedBox(width: 10), Expanded( child: Text( worker.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, letterSpacing: -0.4, color: Colors.white, ), ), ), Text( Fmt.money(scheduled), style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, letterSpacing: -0.5, color: KrowPalette.yellow, ), ), const SizedBox(width: 10), HeaderIconButton( icon: LucideIcons.messageCircle, tooltip: 'Support', onPressed: () => context.push('/support'), ), ], ), ), ), ), SliverPadding( padding: const EdgeInsets.fromLTRB(20, 0, 20, 24), sliver: SliverList.list( children: [ if (!compliant) ...[ const SizedBox(height: 16), KrowCard( onTap: () => context.push('/compliance'), padding: const EdgeInsets.all(14), child: Row( children: [ const Icon( LucideIcons.triangleAlert, size: 20, color: KrowPalette.ink, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Finish your compliance', style: TextStyle( fontSize: 14.5, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 2), Text( '$done of $total items cleared', style: const TextStyle( fontSize: 12, color: KrowPalette.mute, ), ), ], ), ), const StatusChip('Action', tone: ChipTone.yellow), ], ), ), ], const SectionLabel('Up next'), if (next == null) EmptyState( icon: LucideIcons.ticket, title: 'Nothing booked', message: 'Open shifts near you start at ' '${open.isEmpty ? '-' : Fmt.money(open.last.rate)}/hr.', action: FilledButton( onPressed: () => context.go('/shifts'), child: const Text('Browse open shifts'), ), ) else ShiftCard( shift: next, showDate: !Fmt.sameDay(next.start, DateTime.now()), onTap: () => context.push('/shift/${next.id}'), ), if (next != null && next.status == ShiftStatus.active) ...[ const SizedBox(height: 10), FilledButton.icon( onPressed: () => context.go('/clock'), icon: const Icon(LucideIcons.squareCheck, size: 18), label: const Text('You are on shift - go to clock'), ), ], const SizedBox(height: 14), Row( children: [ Expanded( child: _QuickTile( icon: LucideIcons.search, label: 'Find shifts', caption: '${open.length} OPEN', onTap: () => context.go('/shifts'), ), ), const SizedBox(width: 10), Expanded( child: _QuickTile( icon: LucideIcons.calendar, label: 'Availability', caption: '$daysSet DAYS SET', onTap: () => context.push('/availability'), ), ), const SizedBox(width: 10), Expanded( child: _QuickTile( icon: LucideIcons.banknote, label: 'Earnings', caption: 'PENDING', onTap: () => context.go('/pay'), ), ), ], ), SectionLabel( 'Matched to you', trailing: TextButton( onPressed: () => context.go('/shifts'), child: const Text('See all'), ), ), if (open.isEmpty) const InfoBanner( 'No open shifts right now. Turn on more availability blocks ' 'and we will match you to more work.', ) else for (final shift in open.take(3)) Padding( padding: const EdgeInsets.only(bottom: 10), child: ShiftCard( shift: shift, showDate: true, onTap: () => context.push('/shift/${shift.id}'), ), ), ], ), ), ], ), ); } } /// The worker's initials, used at two sizes as the header collapses. class _Avatar extends StatelessWidget { const _Avatar(this.initials, {this.size = 44}); final String initials; final double size; @override Widget build(BuildContext context) { return Container( width: size, height: size, alignment: Alignment.center, decoration: BoxDecoration( color: KrowPalette.yellow, borderRadius: BorderRadius.circular(size / 3.4), ), child: Text( initials, style: TextStyle( fontSize: size * 0.39, fontWeight: FontWeight.w700, color: KrowPalette.ink, ), ), ); } } class _HeaderStat extends StatelessWidget { const _HeaderStat(this.value, this.caption); final String value; final String caption; @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Both lines hold to one line and shrink to fit: a wrapped caption // would push the header past its pinned height. FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text( value, maxLines: 1, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, letterSpacing: -0.8, color: Colors.white, ), ), ), const SizedBox(height: 3), FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text( caption.toUpperCase(), maxLines: 1, style: TextStyle( fontSize: 9.5, letterSpacing: 1.4, fontWeight: FontWeight.w600, color: Colors.white.withValues(alpha: 0.5), ), ), ), ], ); } } class _QuickTile extends StatelessWidget { const _QuickTile({ required this.icon, required this.label, required this.caption, required this.onTap, }); final IconData icon; final String label; final String caption; final VoidCallback onTap; @override Widget build(BuildContext context) { return KrowCard( onTap: onTap, padding: const EdgeInsets.fromLTRB(12, 14, 12, 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 34, height: 34, decoration: BoxDecoration( color: KrowPalette.blueTint, borderRadius: BorderRadius.circular(11), ), child: Icon(icon, size: 17, color: KrowPalette.blue), ), const SizedBox(height: 22), Text( label, style: const TextStyle( fontSize: 13.5, fontWeight: FontWeight.w600, letterSpacing: -0.2, ), ), const SizedBox(height: 2), Text( caption, style: const TextStyle( fontSize: 9.5, fontWeight: FontWeight.w600, letterSpacing: 0.5, color: Color(0xFF949FA5), ), ), ], ), ); } }