Add version files and update imports for trip model; enhance error handling
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class TripDiagramEntry {
|
||||
const TripDiagramEntry({required this.label, this.labelIcon, this.subtitle, this.time});
|
||||
|
||||
final String label;
|
||||
final IconData? labelIcon;
|
||||
final String? subtitle;
|
||||
final String? time;
|
||||
}
|
||||
|
||||
class TripDiagram extends StatelessWidget {
|
||||
const TripDiagram({
|
||||
required this.entries,
|
||||
super.key,
|
||||
this.leftOffset = 0,
|
||||
this.rowHeight = 32,
|
||||
this.lineWidth = 7,
|
||||
this.lineColor,
|
||||
this.highlightLastEntry = false,
|
||||
this.labelOffset = 26,
|
||||
this.rightPadding = 16,
|
||||
this.oddRowColor,
|
||||
this.terminalRowColor,
|
||||
this.stationTextStyle,
|
||||
this.timeTextStyle,
|
||||
this.missingTimeTextStyle,
|
||||
this.emptyMessage = "No stations found.",
|
||||
this.emptyTextStyle,
|
||||
});
|
||||
|
||||
final List<TripDiagramEntry> entries;
|
||||
final double leftOffset;
|
||||
final double rowHeight;
|
||||
final double lineWidth;
|
||||
final Color? lineColor;
|
||||
final bool highlightLastEntry;
|
||||
final double labelOffset;
|
||||
final double rightPadding;
|
||||
final Color? oddRowColor;
|
||||
final Color? terminalRowColor;
|
||||
final TextStyle? stationTextStyle;
|
||||
final TextStyle? timeTextStyle;
|
||||
final TextStyle? missingTimeTextStyle;
|
||||
final String emptyMessage;
|
||||
final TextStyle? emptyTextStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final resolvedLineColor = lineColor ?? Theme.of(context).colorScheme.primary;
|
||||
|
||||
final deduped = entries;
|
||||
|
||||
if (deduped.isEmpty) {
|
||||
return Text(
|
||||
emptyMessage,
|
||||
style:
|
||||
emptyTextStyle ??
|
||||
TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
);
|
||||
}
|
||||
|
||||
final totalHeight = deduped.length * rowHeight;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: totalHeight,
|
||||
width: double.infinity,
|
||||
child: CustomPaint(
|
||||
painter: _TripDiagramPainter(
|
||||
count: deduped.length,
|
||||
rowHeight: rowHeight,
|
||||
lineWidth: lineWidth,
|
||||
lineColor: resolvedLineColor,
|
||||
leftOffset: leftOffset,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Column(
|
||||
children: List.generate(deduped.length, (index) {
|
||||
final entry = deduped[index];
|
||||
final time = entry.time;
|
||||
final isTerminalRow = highlightLastEntry && index == deduped.length - 1;
|
||||
|
||||
return Container(
|
||||
height: rowHeight,
|
||||
color: isTerminalRow
|
||||
? (terminalRowColor ?? resolvedLineColor.withValues(alpha: 0.12))
|
||||
: index.isOdd
|
||||
? (oddRowColor ?? Colors.white.withValues(alpha: 0.03))
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: leftOffset + labelOffset,
|
||||
right: rightPadding,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
entry.label,
|
||||
style:
|
||||
stationTextStyle ??
|
||||
const TextStyle(
|
||||
fontSize: 15,
|
||||
color: Color(0xFFDDDDDD),
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
if (entry.labelIcon != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
entry.labelIcon,
|
||||
size: 12,
|
||||
color: const Color(0xFFBBBBBB),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
if (entry.subtitle != null)
|
||||
Text(
|
||||
entry.subtitle!,
|
||||
style:
|
||||
stationTextStyle ??
|
||||
const TextStyle(
|
||||
fontSize: 11,
|
||||
color: Color(0xFFDDDDDD),
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
time ?? "--:--",
|
||||
style: time != null
|
||||
? (timeTextStyle ??
|
||||
const TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: "monospace",
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFFEEEEEE),
|
||||
))
|
||||
: (missingTimeTextStyle ??
|
||||
const TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: "monospace",
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFF555555),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TripDiagramPainter extends CustomPainter {
|
||||
const _TripDiagramPainter({
|
||||
required this.count,
|
||||
required this.rowHeight,
|
||||
required this.lineWidth,
|
||||
required this.lineColor,
|
||||
this.leftOffset = 0,
|
||||
});
|
||||
|
||||
final int count;
|
||||
final double rowHeight;
|
||||
final double lineWidth;
|
||||
final Color lineColor;
|
||||
final double leftOffset;
|
||||
|
||||
static const double _linePad = 4.0;
|
||||
static const double _blobPad = 4.0;
|
||||
static const double _centerSize = 4.0;
|
||||
static const double _cornerFactor = 0.3;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (count <= 0) return;
|
||||
|
||||
final strokeWidth = lineWidth / 2.0;
|
||||
final ringSize = _centerSize + strokeWidth;
|
||||
final paddingWidth = strokeWidth + _blobPad;
|
||||
final centerX = leftOffset + ringSize / 2 + paddingWidth / 2 + 2;
|
||||
final firstY = rowHeight / 2;
|
||||
final lastY = (count - 1) * rowHeight + rowHeight / 2;
|
||||
|
||||
final nodeRects = List.generate(count, (index) {
|
||||
final centerY = index * rowHeight + rowHeight / 2;
|
||||
final rect = Rect.fromCenter(
|
||||
center: Offset(centerX, centerY),
|
||||
width: ringSize,
|
||||
height: ringSize,
|
||||
);
|
||||
return RRect.fromRectAndRadius(
|
||||
rect,
|
||||
Radius.circular(ringSize * _cornerFactor),
|
||||
);
|
||||
});
|
||||
|
||||
for (final rect in nodeRects) {
|
||||
canvas.drawRRect(
|
||||
rect,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..strokeWidth = paddingWidth
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
}
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(centerX, firstY),
|
||||
Offset(centerX, lastY),
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..strokeWidth = lineWidth + _linePad
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
canvas.drawLine(
|
||||
Offset(centerX, firstY),
|
||||
Offset(centerX, lastY),
|
||||
Paint()
|
||||
..color = lineColor
|
||||
..strokeWidth = lineWidth
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
|
||||
for (final rect in nodeRects) {
|
||||
final centerRect = rect.deflate(strokeWidth / 2);
|
||||
canvas.drawRRect(
|
||||
centerRect,
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.fill,
|
||||
);
|
||||
canvas.drawRRect(
|
||||
rect,
|
||||
Paint()
|
||||
..color = lineColor
|
||||
..strokeWidth = strokeWidth
|
||||
..style = PaintingStyle.stroke,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _TripDiagramPainter oldDelegate) {
|
||||
return oldDelegate.count != count ||
|
||||
oldDelegate.rowHeight != rowHeight ||
|
||||
oldDelegate.lineWidth != lineWidth ||
|
||||
oldDelegate.lineColor != lineColor ||
|
||||
oldDelegate.leftOffset != leftOffset;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user