focus-flow/apps/focus_flow_flutter/lib/widgets/timeline/timeline_geometry.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;
}