add timeline edit/delete
All checks were successful
Release / meta (push) Successful in 8s
Release / linux-build (push) Successful in 6m36s
Release / android-build (push) Successful in 17m20s
Release / release-dev (push) Successful in 37s
Release / release-master (push) Successful in 35s

This commit is contained in:
2025-12-17 12:17:41 +00:00
parent 80be797322
commit fa9773bcd1
9 changed files with 454 additions and 38 deletions

View File

@@ -5,9 +5,13 @@ final DateFormat _dateFormat = DateFormat('yyyy-MM-dd');
class _TimelineGrid extends StatefulWidget {
const _TimelineGrid({
required this.entries,
this.onEditEntry,
this.onDeleteEntry,
});
final List<LocoAttrVersion> entries;
final void Function(LocoAttrVersion entry)? onEditEntry;
final void Function(LocoAttrVersion entry)? onDeleteEntry;
@override
State<_TimelineGrid> createState() => _TimelineGridState();
@@ -185,6 +189,8 @@ class _TimelineGridState extends State<_TimelineGrid> {
model: model,
scrollOffset: _scrollOffset,
viewportWidth: axisWidth,
onEditEntry: widget.onEditEntry,
onDeleteEntry: widget.onDeleteEntry,
),
);
},
@@ -268,6 +274,8 @@ class _AttrRow extends StatelessWidget {
required this.model,
required this.scrollOffset,
required this.viewportWidth,
this.onEditEntry,
this.onDeleteEntry,
});
final double rowHeight;
@@ -275,6 +283,8 @@ class _AttrRow extends StatelessWidget {
final _TimelineModel model;
final double scrollOffset;
final double viewportWidth;
final void Function(LocoAttrVersion entry)? onEditEntry;
final void Function(LocoAttrVersion entry)? onDeleteEntry;
@override
Widget build(BuildContext context) {
@@ -296,7 +306,11 @@ class _AttrRow extends StatelessWidget {
width: block.width,
top: 0,
bottom: 0,
child: _ValueBlockView(block: block),
child: _ValueBlockMenu(
block: block,
onEditEntry: onEditEntry,
onDeleteEntry: onDeleteEntry,
),
),
if (activeBlock != null)
Positioned(
@@ -408,6 +422,80 @@ class _ValueBlockView extends StatelessWidget {
}
}
enum _TimelineBlockAction { edit, delete }
class _ValueBlockMenu extends StatelessWidget {
const _ValueBlockMenu({
required this.block,
this.onEditEntry,
this.onDeleteEntry,
});
final _ValueBlock block;
final void Function(LocoAttrVersion entry)? onEditEntry;
final void Function(LocoAttrVersion entry)? onDeleteEntry;
bool get _hasActions => onEditEntry != null || onDeleteEntry != null;
@override
Widget build(BuildContext context) {
if (!_hasActions || block.entry == null) {
return _ValueBlockView(block: block);
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onLongPressStart: (details) async {
final overlay = Overlay.of(context);
final renderBox = overlay.context.findRenderObject() as RenderBox?;
if (renderBox == null) return;
if (defaultTargetPlatform == TargetPlatform.android) {
HapticFeedback.lightImpact();
}
final anchor = details.globalPosition + const Offset(0, -8);
final position = RelativeRect.fromRect(
Rect.fromLTWH(
anchor.dx,
anchor.dy,
1,
1,
),
Offset.zero & renderBox.size,
);
final action = await showMenu<_TimelineBlockAction>(
context: context,
position: position,
items: [
if (onEditEntry != null)
const PopupMenuItem(
value: _TimelineBlockAction.edit,
child: Text('Edit'),
),
if (onDeleteEntry != null)
const PopupMenuItem(
value: _TimelineBlockAction.delete,
child: Text('Delete'),
),
],
);
final entry = block.entry;
if (action == null || entry == null) return;
switch (action) {
case _TimelineBlockAction.edit:
onEditEntry?.call(entry);
break;
case _TimelineBlockAction.delete:
onDeleteEntry?.call(entry);
break;
}
},
child: _ValueBlockView(block: block),
);
}
}
String? _formatDate(DateTime? date) {
if (date == null) return null;
return _dateFormat.format(date);
@@ -481,23 +569,14 @@ class _TimelineModel {
: null;
final rawEnd = entry.validTo ?? nextStart ?? now;
final end = _safeEnd(start, rawEnd);
if (segments.isNotEmpty && segments.last.value == entry.valueLabel) {
final last = segments.removeLast();
segments.add(
last.copyWith(
end: end.isAfter(last.end) ? end : last.end,
),
);
} else {
segments.add(
_ValueSegment(
start: start,
end: end,
value: entry.valueLabel,
entry: entry,
),
);
}
segments.add(
_ValueSegment(
start: start,
end: end,
value: entry.valueLabel,
entry: entry,
),
);
minStart = minStart == null || start.isBefore(minStart!)
? start
: minStart;
@@ -557,6 +636,7 @@ class _TimelineModel {
left: left,
width: width,
cell: _RowCell.fromSegment(seg),
entry: seg.entry,
),
);
}
@@ -671,11 +751,13 @@ class _ValueBlock {
final double left;
final double width;
final _RowCell cell;
final LocoAttrVersion? entry;
const _ValueBlock({
required this.left,
required this.width,
required this.cell,
required this.entry,
});
double get right => left + width;
@@ -684,11 +766,13 @@ class _ValueBlock {
double? left,
double? width,
_RowCell? cell,
LocoAttrVersion? entry,
}) {
return _ValueBlock(
left: left ?? this.left,
width: width ?? this.width,
cell: cell ?? this.cell,
entry: entry ?? this.entry,
);
}
}