always set lane

This commit is contained in:
2026-02-25 20:00:43 +01:00
parent ea22da9e5a
commit f3b645ac53
13 changed files with 325 additions and 102 deletions

View File

@@ -64,35 +64,25 @@ class _MainAppState extends State<MainApp> {
}
List<TimelineGroup> _convertGroups(List<TimelineGroupData> groups) {
return [
for (final g in groups) TimelineGroup(id: g.id, title: g.title),
];
return [for (final g in groups) TimelineGroup(id: g.id, title: g.title)];
}
List<TimelineEntry> _convertEntries(List<TimelineGroupData> groups) {
final entries = <TimelineEntry>[];
for (final group in groups) {
// Collect all items for this group to compute lanes
final groupItems = group.items;
final sorted = [...groupItems]..sort(
(a, b) => a.start.compareTo(b.start),
);
for (final item in sorted) {
for (final item in group.items) {
final start = DateTime.parse(item.start);
final end = item.end != null
? DateTime.parse(item.end!)
: start.add(const Duration(days: 1));
final lane = _assignLane(entries, group.id, start, end);
entries.add(
TimelineEntry(
id: item.id,
groupId: group.id,
start: start,
end: end,
lane: lane,
lane: item.lane,
),
);
}
@@ -100,26 +90,13 @@ class _MainAppState extends State<MainApp> {
return entries;
}
int _assignLane(
List<TimelineEntry> existing,
String groupId,
DateTime start,
DateTime end,
) {
final groupEntries = existing.where((e) => e.groupId == groupId);
for (var lane = 1; lane <= 100; lane++) {
final hasConflict = groupEntries.any(
(e) => e.lane == lane && e.overlaps(start, end),
);
if (!hasConflict) return lane;
}
return 1;
}
({DateTime start, DateTime end}) _computeDomain(List<TimelineEntry> entries) {
if (entries.isEmpty) {
final now = DateTime.now();
return (start: now.subtract(const Duration(days: 30)), end: now.add(const Duration(days: 30)));
return (
start: now.subtract(const Duration(days: 30)),
end: now.add(const Duration(days: 30)),
);
}
var earliest = entries.first.start;
@@ -132,10 +109,7 @@ class _MainAppState extends State<MainApp> {
// Add 10% padding on each side
final span = latest.difference(earliest);
final padding = Duration(milliseconds: (span.inMilliseconds * 0.1).round());
return (
start: earliest.subtract(padding),
end: latest.add(padding),
);
return (start: earliest.subtract(padding), end: latest.add(padding));
}
void _onEntryMoved(
@@ -144,34 +118,16 @@ class _MainAppState extends State<MainApp> {
String newGroupId,
int newLane,
) {
// Emit event to React via bridge
final duration = entry.end.difference(entry.start);
final newEnd = newStart.add(duration);
emitEvent('entry_moved', {
'entryId': entry.id,
'newStart': newStart.toIso8601String(),
'newEnd': newEnd.toIso8601String(),
'newGroupId': newGroupId,
'newLane': newLane,
});
// Update local state so Flutter UI reflects the move immediately
setState(() {
final duration = entry.end.difference(entry.start);
final newEnd = newStart.add(duration);
_entries = [
for (final e in _entries)
if (e.id == entry.id)
e.copyWith(
groupId: newGroupId,
start: newStart,
end: newEnd,
lane: newLane,
)
else
e,
];
});
_emitContentHeight();
}
void _emitContentHeight() {
@@ -184,9 +140,12 @@ class _MainAppState extends State<MainApp> {
if (e.lane > maxLane) maxLane = e.lane;
}
final lanesCount = maxLane.clamp(0, 1000);
totalHeight += lanesCount * ZTimelineConstants.laneHeight
+ (lanesCount > 0 ? (lanesCount - 1) * ZTimelineConstants.laneVerticalSpacing : 0)
+ ZTimelineConstants.verticalOuterPadding * 2;
totalHeight +=
lanesCount * ZTimelineConstants.laneHeight +
(lanesCount > 0
? (lanesCount - 1) * ZTimelineConstants.laneVerticalSpacing
: 0) +
ZTimelineConstants.verticalOuterPadding * 2;
}
emitEvent('content_height', {'height': totalHeight});
}