99 lines
2.7 KiB
Dart
99 lines
2.7 KiB
Dart
/// Renders Timeline Geometry pieces for the FocusFlow Flutter timeline.
|
|
library;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// Converts timeline clock values into vertical pixel positions.
|
|
class TimelineGeometry {
|
|
/// Creates geometry for a visible time range.
|
|
const TimelineGeometry({
|
|
required this.visibleStart,
|
|
required this.visibleEnd,
|
|
required this.pixelsPerMinute,
|
|
this.minCardHeight = 72,
|
|
});
|
|
|
|
/// First visible time on the timeline.
|
|
final TimeOfDay visibleStart;
|
|
|
|
/// Last visible time on the timeline.
|
|
final TimeOfDay visibleEnd;
|
|
|
|
/// Vertical scale used to convert minutes into pixels.
|
|
final double pixelsPerMinute;
|
|
|
|
/// Minimum rendered height for a timeline card.
|
|
final double minCardHeight;
|
|
|
|
/// First visible minute from midnight.
|
|
int get startMinutes => visibleStart.hour * 60 + visibleStart.minute;
|
|
|
|
/// Last visible minute from midnight.
|
|
int get endMinutes => visibleEnd.hour * 60 + visibleEnd.minute;
|
|
|
|
/// 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<TimelineTick> 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')}';
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
}
|