/// Renders Timeline Geometry pieces for the FocusFlow Flutter timeline. library; import 'package:flutter/material.dart'; class TimelineGeometry { const TimelineGeometry({ required this.visibleStart, required this.visibleEnd, required this.pixelsPerMinute, this.minCardHeight = 72, }); final TimeOfDay visibleStart; final TimeOfDay visibleEnd; final double pixelsPerMinute; final double minCardHeight; int get startMinutes => visibleStart.hour * 60 + visibleStart.minute; int get endMinutes => visibleEnd.hour * 60 + visibleEnd.minute; double get totalHeight => (endMinutes - startMinutes) * pixelsPerMinute; double yForMinutesSinceMidnight(int minutes) { return (minutes - startMinutes) * pixelsPerMinute; } double heightForDuration(int minutes) { final height = minutes * pixelsPerMinute; return height < minCardHeight ? minCardHeight : height; } List ticks({int stepMinutes = 15}) { return [ for ( var minute = startMinutes; minute <= endMinutes; minute += stepMinutes ) TimelineTick( minutesSinceMidnight: minute, y: yForMinutesSinceMidnight(minute), label: _labelFor(minute), major: minute % 60 == 0, ), ]; } static String _labelFor(int minutes) { final hour24 = minutes ~/ 60; final minute = minutes % 60; final period = hour24 >= 12 ? 'PM' : 'AM'; final rawHour = hour24 % 12; final hour = rawHour == 0 ? 12 : rawHour; if (minute == 0) { return '$hour:00 $period'; } return '$hour:${minute.toString().padLeft(2, '0')}'; } } class TimelineTick { const TimelineTick({ required this.minutesSinceMidnight, required this.y, required this.label, required this.major, }); final int minutesSinceMidnight; final double y; final String label; final bool major; }