/// Renders Timeline Geometry pieces for the FocusFlow Flutter timeline. library; /// Converts timeline clock values into vertical pixel positions. class TimelineGeometry { /// Creates geometry for a visible time range. const TimelineGeometry({ required this.startMinutes, required this.endMinutes, required this.pixelsPerMinute, this.minCardHeight = 72, }) : assert(startMinutes >= 0), assert(endMinutes <= 24 * 60), assert(endMinutes > startMinutes); /// First visible minute from midnight. final int startMinutes; /// Last visible minute from midnight. final int endMinutes; /// Vertical scale used to convert minutes into pixels. final double pixelsPerMinute; /// Minimum rendered height for a timeline card. final double minCardHeight; /// Total vertical height of the visible timeline. double get totalHeight => (endMinutes - startMinutes) * pixelsPerMinute; /// Returns the vertical offset for [minutes] since midnight. double yForMinutesSinceMidnight(int minutes) { return (minutes - startMinutes) * pixelsPerMinute; } /// Returns the rendered height for a duration in minutes. double heightForDuration(int minutes) { final height = minutes * pixelsPerMinute; return height < minCardHeight ? minCardHeight : height; } /// Generates timeline ticks at [stepMinutes] intervals. 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) % 24; 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')}'; } } /// A labeled tick on the compact timeline axis. class TimelineTick { /// Creates a timeline tick. const TimelineTick({ required this.minutesSinceMidnight, required this.y, required this.label, required this.major, }); /// Minute from midnight represented by this tick. final int minutesSinceMidnight; /// Vertical pixel offset for this tick. final double y; /// Display label for this tick. final String label; /// Whether this tick represents a major interval. final bool major; }