@@ -182,11 +182,22 @@ class _LogbookScreenState extends State<LogbookScreen> {
182182 final initialStart =
183183 _customRange? .start ?? now.subtract (const Duration (days: 7 ));
184184 final initialEnd = _customRange? .end ?? now;
185- final picked = await showDateRangePicker (
185+ final picked = await showModalBottomSheet < DateTimeRange > (
186186 context: context,
187- firstDate: DateTime (2020 ),
188- lastDate: now.add (const Duration (days: 365 )),
189- initialDateRange: DateTimeRange (start: initialStart, end: initialEnd),
187+ showDragHandle: true ,
188+ isScrollControlled: true ,
189+ builder: (ctx) {
190+ final base = Theme .of (ctx);
191+ final themed = base .copyWith (
192+ colorScheme: base .colorScheme.copyWith (primary: AppTheme .primary),
193+ );
194+ return Theme (
195+ data: themed,
196+ child: _CompactRangePickerSheet (
197+ initial: DateTimeRange (start: initialStart, end: initialEnd),
198+ ),
199+ );
200+ },
190201 );
191202 if (picked == null ) return ;
192203 if (! context.mounted) return ;
@@ -281,6 +292,22 @@ class _HistoryHeader extends StatelessWidget {
281292 ),
282293 ),
283294 SegmentedButton <_HistoryRange >(
295+ style: ButtonStyle (
296+ side: const WidgetStatePropertyAll (BorderSide .none),
297+ backgroundColor: WidgetStateProperty .resolveWith (
298+ (states) => states.contains (WidgetState .selected)
299+ ? AppTheme .primary.withValues (alpha: 0.18 )
300+ : AppTheme .surfaceLightFor (context),
301+ ),
302+ foregroundColor: WidgetStateProperty .resolveWith (
303+ (states) => states.contains (WidgetState .selected)
304+ ? AppTheme .primary
305+ : AppTheme .textMutedFor (context),
306+ ),
307+ overlayColor: WidgetStatePropertyAll (
308+ AppTheme .primary.withValues (alpha: 0.10 ),
309+ ),
310+ ),
284311 segments: const [
285312 ButtonSegment (value: _HistoryRange .day, label: Text ('Day' )),
286313 ButtonSegment (value: _HistoryRange .week, label: Text ('Week' )),
@@ -294,6 +321,10 @@ class _HistoryHeader extends StatelessWidget {
294321 IconButton .filledTonal (
295322 tooltip: 'Pick date range' ,
296323 onPressed: onPickCustomRange,
324+ style: IconButton .styleFrom (
325+ backgroundColor: AppTheme .primary.withValues (alpha: 0.18 ),
326+ foregroundColor: AppTheme .primary,
327+ ),
297328 icon: const Icon (Icons .date_range_outlined),
298329 ),
299330 IconButton (
@@ -341,6 +372,235 @@ class _HistoryHeader extends StatelessWidget {
341372 }
342373}
343374
375+ class _CompactRangePickerSheet extends StatefulWidget {
376+ final DateTimeRange initial;
377+ const _CompactRangePickerSheet ({required this .initial});
378+
379+ @override
380+ State <_CompactRangePickerSheet > createState () =>
381+ _CompactRangePickerSheetState ();
382+ }
383+
384+ class _CompactRangePickerSheetState extends State <_CompactRangePickerSheet > {
385+ late DateTime _start;
386+ late DateTime _end;
387+ bool _pickingStart = true ;
388+
389+ @override
390+ void initState () {
391+ super .initState ();
392+ _start = DateTime (
393+ widget.initial.start.year,
394+ widget.initial.start.month,
395+ widget.initial.start.day,
396+ );
397+ _end = DateTime (
398+ widget.initial.end.year,
399+ widget.initial.end.month,
400+ widget.initial.end.day,
401+ );
402+ if (_end.isBefore (_start)) _end = _start;
403+ }
404+
405+ @override
406+ Widget build (BuildContext context) {
407+ final maxH = MediaQuery .of (context).size.height * 0.72 ;
408+ return SafeArea (
409+ child: ConstrainedBox (
410+ constraints: BoxConstraints (maxHeight: maxH),
411+ child: Padding (
412+ padding: const EdgeInsets .fromLTRB (16 , 0 , 16 , 16 ),
413+ child: Column (
414+ children: [
415+ Row (
416+ children: [
417+ Text (
418+ 'Date range' ,
419+ style: TextStyle (
420+ color: AppTheme .textPrimaryFor (context),
421+ fontWeight: FontWeight .w700,
422+ fontSize: 14 ,
423+ ),
424+ ),
425+ const Spacer (),
426+ TextButton (
427+ onPressed: () {
428+ final now = DateTime .now ();
429+ setState (() {
430+ _start = DateTime (now.year, now.month, now.day);
431+ _end = _start;
432+ _pickingStart = true ;
433+ });
434+ },
435+ child: Text ('Today' ,
436+ style: TextStyle (color: AppTheme .primary)),
437+ ),
438+ const SizedBox (width: 6 ),
439+ TextButton (
440+ onPressed: () {
441+ final now = DateTime .now ();
442+ setState (() {
443+ _end = DateTime (now.year, now.month, now.day);
444+ _start = _end.subtract (const Duration (days: 7 ));
445+ _pickingStart = false ;
446+ });
447+ },
448+ child:
449+ Text ('7d' , style: TextStyle (color: AppTheme .primary)),
450+ ),
451+ const SizedBox (width: 6 ),
452+ TextButton (
453+ onPressed: () {
454+ final now = DateTime .now ();
455+ setState (() {
456+ _end = DateTime (now.year, now.month, now.day);
457+ _start = _end.subtract (const Duration (days: 30 ));
458+ _pickingStart = false ;
459+ });
460+ },
461+ child:
462+ Text ('30d' , style: TextStyle (color: AppTheme .primary)),
463+ ),
464+ ],
465+ ),
466+ const SizedBox (height: 8 ),
467+ Container (
468+ padding: const EdgeInsets .all (12 ),
469+ decoration: BoxDecoration (
470+ color: AppTheme .surfaceLightFor (context),
471+ borderRadius: BorderRadius .circular (12 ),
472+ ),
473+ child: Row (
474+ children: [
475+ _RangeChip (
476+ label: 'Start' ,
477+ value: _fmt (_start),
478+ active: _pickingStart,
479+ onTap: () => setState (() => _pickingStart = true ),
480+ ),
481+ const SizedBox (width: 10 ),
482+ _RangeChip (
483+ label: 'End' ,
484+ value: _fmt (_end),
485+ active: ! _pickingStart,
486+ onTap: () => setState (() => _pickingStart = false ),
487+ ),
488+ const Spacer (),
489+ FilledButton (
490+ onPressed: () => Navigator .pop (
491+ context,
492+ DateTimeRange (start: _start, end: _end),
493+ ),
494+ child: const Text ('Apply' ),
495+ ),
496+ ],
497+ ),
498+ ),
499+ const SizedBox (height: 12 ),
500+ Expanded (
501+ child: CalendarDatePicker (
502+ initialDate: _pickingStart ? _start : _end,
503+ firstDate: DateTime (2020 ),
504+ lastDate: DateTime .now ().add (const Duration (days: 365 )),
505+ onDateChanged: (d) {
506+ setState (() {
507+ final day = DateTime (d.year, d.month, d.day);
508+ if (_pickingStart) {
509+ _start = day;
510+ if (_end.isBefore (_start)) _end = _start;
511+ } else {
512+ _end = day;
513+ if (_end.isBefore (_start)) _start = _end;
514+ }
515+ });
516+ },
517+ ),
518+ ),
519+ Row (
520+ children: [
521+ TextButton (
522+ onPressed: () => Navigator .pop (context),
523+ child: const Text ('Cancel' ),
524+ ),
525+ const Spacer (),
526+ Text (
527+ '${_fmt (_start )} → ${_fmt (_end )}' ,
528+ style: TextStyle (
529+ color: AppTheme .textMutedFor (context),
530+ fontSize: 11 ,
531+ ),
532+ ),
533+ ],
534+ ),
535+ ],
536+ ),
537+ ),
538+ ),
539+ );
540+ }
541+
542+ String _fmt (DateTime dt) {
543+ String two (int v) => v < 10 ? '0$v ' : '$v ' ;
544+ return '${dt .year }-${two (dt .month )}-${two (dt .day )}' ;
545+ }
546+ }
547+
548+ class _RangeChip extends StatelessWidget {
549+ final String label;
550+ final String value;
551+ final bool active;
552+ final VoidCallback onTap;
553+
554+ const _RangeChip ({
555+ required this .label,
556+ required this .value,
557+ required this .active,
558+ required this .onTap,
559+ });
560+
561+ @override
562+ Widget build (BuildContext context) {
563+ final bg = active
564+ ? AppTheme .primary.withValues (alpha: 0.16 )
565+ : Theme .of (context).colorScheme.surface;
566+ final fg = active ? AppTheme .primary : AppTheme .textSecondaryFor (context);
567+ return InkWell (
568+ borderRadius: BorderRadius .circular (12 ),
569+ onTap: onTap,
570+ child: Container (
571+ padding: const EdgeInsets .symmetric (horizontal: 12 , vertical: 10 ),
572+ decoration: BoxDecoration (
573+ color: bg,
574+ borderRadius: BorderRadius .circular (12 ),
575+ ),
576+ child: Column (
577+ crossAxisAlignment: CrossAxisAlignment .start,
578+ mainAxisSize: MainAxisSize .min,
579+ children: [
580+ Text (
581+ label.toUpperCase (),
582+ style: TextStyle (
583+ color: AppTheme .textMutedFor (context),
584+ fontSize: 9 ,
585+ fontWeight: FontWeight .w700,
586+ letterSpacing: 0.6 ,
587+ ),
588+ ),
589+ const SizedBox (height: 2 ),
590+ Text (
591+ value,
592+ style: TextStyle (
593+ color: fg,
594+ fontWeight: FontWeight .w700,
595+ ),
596+ ),
597+ ],
598+ ),
599+ ),
600+ );
601+ }
602+ }
603+
344604class _EmptyHistory extends StatelessWidget {
345605 final bool loaded;
346606 const _EmptyHistory ({required this .loaded});
0 commit comments