ui: tweak calibration sheet spacing
This commit is contained in:
@@ -132,15 +132,8 @@ class _CalibrationSheetState extends ConsumerState<CalibrationSheet> {
|
|||||||
sensorKey: _key,
|
sensorKey: _key,
|
||||||
onNext: _commitTag,
|
onNext: _commitTag,
|
||||||
),
|
),
|
||||||
_CollectingPage(
|
_CollectingPage(state: state, sensorKey: _key, onFinish: _finish),
|
||||||
state: state,
|
_DonePage(state: state, onDone: () => _done(context)),
|
||||||
sensorKey: _key,
|
|
||||||
onFinish: _finish,
|
|
||||||
),
|
|
||||||
_DonePage(
|
|
||||||
state: state,
|
|
||||||
onDone: () => _done(context),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -179,37 +172,37 @@ class _IntroPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
Icon(
|
Icon(Icons.tune, size: 56, color: theme.colorScheme.primary),
|
||||||
Icons.tune,
|
const SizedBox(height: 18),
|
||||||
size: 56,
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Text(
|
Text(
|
||||||
'Calibrate sensor',
|
'Calibrate sensor',
|
||||||
style: theme.textTheme.headlineSmall
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
?.copyWith(fontWeight: FontWeight.w600),
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Calibration improves distance estimation accuracy. You will hold a tag at a series of known distances from the sensor while it collects readings.',
|
'Calibration improves distance estimation accuracy. You will hold a tag at a series of known distances from the sensor while it collects readings.',
|
||||||
style: theme.textTheme.bodyMedium
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
?.copyWith(color: theme.colorScheme.onSurfaceVariant),
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'You will need at least two distance measurements to complete calibration. More distances improve accuracy.',
|
'You will need at least two distance measurements to complete calibration. More distances improve accuracy.',
|
||||||
style: theme.textTheme.bodyMedium
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
?.copyWith(color: theme.colorScheme.onSurfaceVariant),
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'Keep the path between the tag and sensor unobstructed during each measurement.',
|
'Keep the path between the tag and sensor unobstructed during each measurement.',
|
||||||
style: theme.textTheme.bodyMedium
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
?.copyWith(color: theme.colorScheme.onSurfaceVariant),
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
@@ -219,10 +212,7 @@ class _IntroPage extends StatelessWidget {
|
|||||||
icon: Icons.arrow_forward,
|
icon: Icons.arrow_forward,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
TextButton(
|
TextButton(onPressed: onCancel, child: const Text('Cancel')),
|
||||||
onPressed: onCancel,
|
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -256,7 +246,8 @@ class _TagSelectionPage extends ConsumerWidget {
|
|||||||
|
|
||||||
// Sort: tags with readings by RSSI descending (nearest first),
|
// Sort: tags with readings by RSSI descending (nearest first),
|
||||||
// then tags without readings by id.
|
// then tags without readings by id.
|
||||||
final sorted = [...tags]..sort((a, b) {
|
final sorted = [...tags]
|
||||||
|
..sort((a, b) {
|
||||||
final ra = readings[a.tagId];
|
final ra = readings[a.tagId];
|
||||||
final rb = readings[b.tagId];
|
final rb = readings[b.tagId];
|
||||||
if (ra != null && rb != null) return rb.compareTo(ra);
|
if (ra != null && rb != null) return rb.compareTo(ra);
|
||||||
@@ -281,15 +272,15 @@ class _TagSelectionPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 24),
|
||||||
const Text(
|
Text(
|
||||||
'Select your tag',
|
'Select your tag',
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Hold each tag near the sensor — the one you\'re using will show a stronger signal.',
|
'Hold the tag near the sensor — the one you\'re using will show a stronger signal.',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
@@ -303,7 +294,8 @@ class _TagSelectionPage extends ConsumerWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'No tags enrolled',
|
'No tags enrolled',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.onSurfaceVariant),
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
@@ -336,6 +328,15 @@ class _TagSelectionPage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: use 3rd party library
|
||||||
|
String _formatLastSeen(DateTime dt) {
|
||||||
|
final diff = DateTime.now().difference(dt);
|
||||||
|
if (diff.inSeconds < 60) return 'Just now';
|
||||||
|
if (diff.inMinutes < 60) return '${diff.inMinutes}m ago';
|
||||||
|
if (diff.inHours < 24) return '${diff.inHours}h ago';
|
||||||
|
return '${diff.inDays}d ago';
|
||||||
|
}
|
||||||
|
|
||||||
class _TagListTile extends StatelessWidget {
|
class _TagListTile extends StatelessWidget {
|
||||||
const _TagListTile({
|
const _TagListTile({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -363,22 +364,17 @@ class _TagListTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
title: Text(tag.name),
|
title: Text(tag.name),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
tag.tagId,
|
"Last seen ${_formatLastSeen(tag.lastSeen!)}",
|
||||||
style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant),
|
style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
trailing: rssi != null
|
trailing: rssi != null
|
||||||
? TweenAnimationBuilder<double>(
|
? Chip(
|
||||||
tween: Tween(begin: rssi, end: rssi),
|
label: Text(
|
||||||
duration: const Duration(milliseconds: 300),
|
'${rssi!.round()} dBm',
|
||||||
builder: (context, value, child) => Chip(
|
style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
|
||||||
label: Text(
|
|
||||||
'${value.round()} dBm',
|
|
||||||
style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
|
|
||||||
),
|
|
||||||
backgroundColor: cs.primaryContainer,
|
|
||||||
side: BorderSide.none,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
||||||
),
|
),
|
||||||
|
side: BorderSide.none,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
)
|
)
|
||||||
: Chip(
|
: Chip(
|
||||||
label: Text(
|
label: Text(
|
||||||
@@ -431,7 +427,7 @@ class _CollectingPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Dot-dash stage indicator
|
// Dot-dash stage indicator
|
||||||
_StageIndicator(
|
_StageIndicator(
|
||||||
@@ -439,19 +435,16 @@ class _CollectingPage extends ConsumerWidget {
|
|||||||
completedDistances: state.completedDistances,
|
completedDistances: state.completedDistances,
|
||||||
selectedDistance: state.selectedDistance,
|
selectedDistance: state.selectedDistance,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 18),
|
||||||
|
|
||||||
// Title + subtitle
|
// Title + subtitle
|
||||||
Text(
|
Text(
|
||||||
collecting
|
collecting
|
||||||
? 'Hold steady'
|
? 'Hold steady'
|
||||||
: state.selectedDistance != null
|
: state.selectedDistance != null
|
||||||
? 'Step to ${_fmtDist(state.selectedDistance!)} metres'
|
? 'Step to ${_fmtDist(state.selectedDistance!)} metres'
|
||||||
: 'Select a distance',
|
: 'Select a distance',
|
||||||
style: const TextStyle(
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@@ -465,7 +458,7 @@ class _CollectingPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 18),
|
||||||
|
|
||||||
// Distance chips
|
// Distance chips
|
||||||
_DistanceChips(
|
_DistanceChips(
|
||||||
@@ -475,7 +468,7 @@ class _CollectingPage extends ConsumerWidget {
|
|||||||
enabled: !collecting,
|
enabled: !collecting,
|
||||||
onSelect: notifier.selectDistance,
|
onSelect: notifier.selectDistance,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 28),
|
||||||
|
|
||||||
// Ring + pulse
|
// Ring + pulse
|
||||||
Center(
|
Center(
|
||||||
@@ -484,7 +477,7 @@ class _CollectingPage extends ConsumerWidget {
|
|||||||
onStart: collecting ? null : notifier.startStage,
|
onStart: collecting ? null : notifier.startStage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 28),
|
||||||
|
|
||||||
// Stats row
|
// Stats row
|
||||||
_StatsRow(
|
_StatsRow(
|
||||||
@@ -492,15 +485,18 @@ class _CollectingPage extends ConsumerWidget {
|
|||||||
samples: state.samplesCollected,
|
samples: state.samplesCollected,
|
||||||
avgRssi: state.avgRssi,
|
avgRssi: state.avgRssi,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 28),
|
||||||
|
|
||||||
// Waveform
|
// Waveform
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 72,
|
height: 72,
|
||||||
child: CustomPaint(
|
child: Padding(
|
||||||
painter: _WaveformPainter(
|
padding: const EdgeInsetsGeometry.symmetric(horizontal: 8),
|
||||||
readings: state.waveform,
|
child: CustomPaint(
|
||||||
color: theme.colorScheme.primary,
|
painter: _WaveformPainter(
|
||||||
|
readings: state.waveform,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -562,21 +558,23 @@ class _DonePage extends StatelessWidget {
|
|||||||
size: 80,
|
size: 80,
|
||||||
color: Colors.green.shade600,
|
color: Colors.green.shade600,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 18),
|
||||||
Text(
|
Text(
|
||||||
'Calibration complete',
|
'Calibration complete',
|
||||||
style: theme.textTheme.headlineSmall
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
?.copyWith(fontWeight: FontWeight.w600),
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'The sensor model has been updated with your measurements.',
|
'The sensor model has been updated with your measurements.',
|
||||||
style: theme.textTheme.bodyMedium
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
?.copyWith(color: theme.colorScheme.onSurfaceVariant),
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 16),
|
||||||
if (rssiRef != null && exp != null) ...[
|
if (rssiRef != null && exp != null) ...[
|
||||||
_ResultRow(
|
_ResultRow(
|
||||||
label: 'RSSI at 1 m (A)',
|
label: 'RSSI at 1 m (A)',
|
||||||
@@ -586,9 +584,9 @@ class _DonePage extends StatelessWidget {
|
|||||||
label: 'Path loss exponent (n)',
|
label: 'Path loss exponent (n)',
|
||||||
value: exp.toStringAsFixed(3),
|
value: exp.toStringAsFixed(3),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
// const SizedBox(height: 12),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 160,
|
height: 240,
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: _ModelCurvePainter(
|
painter: _ModelCurvePainter(
|
||||||
rssiRef: rssiRef,
|
rssiRef: rssiRef,
|
||||||
@@ -601,15 +599,7 @@ class _DonePage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
FilledButton(
|
FilledButton(onPressed: onDone, child: const Text('Done')),
|
||||||
style: FilledButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(14),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: onDone,
|
|
||||||
child: const Text('Done'),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -649,8 +639,8 @@ class _StageIndicator extends StatelessWidget {
|
|||||||
color: isCompleted
|
color: isCompleted
|
||||||
? Colors.green.shade500
|
? Colors.green.shade500
|
||||||
: isCurrent
|
: isCurrent
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Theme.of(context).colorScheme.outlineVariant,
|
: Theme.of(context).colorScheme.outlineVariant,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -698,8 +688,8 @@ class _DistanceChips extends StatelessWidget {
|
|||||||
color: completed
|
color: completed
|
||||||
? Colors.black
|
? Colors.black
|
||||||
: selected
|
: selected
|
||||||
? cs.onPrimaryContainer
|
? cs.onPrimaryContainer
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
disabledColor: completed ? Colors.green.shade50 : null,
|
disabledColor: completed ? Colors.green.shade50 : null,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@@ -768,7 +758,11 @@ class _RingAreaState extends State<_RingArea> with TickerProviderStateMixin {
|
|||||||
children: [
|
children: [
|
||||||
// One independent pulse ring per in-flight animation.
|
// One independent pulse ring per in-flight animation.
|
||||||
for (final ctrl in _pulses)
|
for (final ctrl in _pulses)
|
||||||
_PulseRing(controller: ctrl, color: theme.colorScheme.primary, size: size),
|
_PulseRing(
|
||||||
|
controller: ctrl,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
size: size,
|
||||||
|
),
|
||||||
|
|
||||||
// Ring
|
// Ring
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
@@ -931,15 +925,9 @@ class _ResultRow extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(label, style: Theme.of(context).textTheme.bodySmall),
|
||||||
label,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: const TextStyle(fontFamily: 'monospace'),
|
|
||||||
),
|
),
|
||||||
|
Text(value, style: const TextStyle(fontFamily: 'monospace')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -984,15 +972,21 @@ class _AsyncButtonState extends State<_AsyncButton> {
|
|||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
)
|
)
|
||||||
: widget.icon != null
|
: widget.icon != null
|
||||||
? Row(
|
? Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(widget.label),
|
Text(widget.label),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Icon(widget.icon, size: 18),
|
Icon(widget.icon, size: 18),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: Text(widget.label, style: TextStyle(fontSize: 36, color: Theme.of(context).colorScheme.primary));
|
: Text(
|
||||||
|
widget.label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 36,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final canPress = !_loading && widget.enabled;
|
final canPress = !_loading && widget.enabled;
|
||||||
|
|
||||||
@@ -1014,17 +1008,17 @@ class _AsyncButtonState extends State<_AsyncButton> {
|
|||||||
|
|
||||||
if (widget.compact) {
|
if (widget.compact) {
|
||||||
return TextButton(
|
return TextButton(
|
||||||
style: TextButton.styleFrom(shape: shape, textStyle: const TextStyle(fontSize: 36, color: Colors.black)),
|
style: TextButton.styleFrom(
|
||||||
|
shape: shape,
|
||||||
|
textStyle: const TextStyle(fontSize: 36, color: Colors.black),
|
||||||
|
),
|
||||||
onPressed: canPress ? _run : null,
|
onPressed: canPress ? _run : null,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FilledButton(
|
return FilledButton(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(minimumSize: const Size.fromHeight(48)),
|
||||||
shape: shape,
|
|
||||||
minimumSize: const Size.fromHeight(48),
|
|
||||||
),
|
|
||||||
onPressed: canPress ? _run : null,
|
onPressed: canPress ? _run : null,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
@@ -1116,7 +1110,10 @@ class _WaveformPainter extends CustomPainter {
|
|||||||
final path = Path();
|
final path = Path();
|
||||||
for (var i = 0; i < readings.length; i++) {
|
for (var i = 0; i < readings.length; i++) {
|
||||||
final x = size.width * i / (readings.length - 1);
|
final x = size.width * i / (readings.length - 1);
|
||||||
final norm = ((readings[i] - _minRssi) / (_maxRssi - _minRssi)).clamp(0.0, 1.0);
|
final norm = ((readings[i] - _minRssi) / (_maxRssi - _minRssi)).clamp(
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
final y = size.height * (1 - norm);
|
final y = size.height * (1 - norm);
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
path.moveTo(x, y);
|
path.moveTo(x, y);
|
||||||
@@ -1151,7 +1148,8 @@ class _ModelCurvePainter extends CustomPainter {
|
|||||||
static const _rssiMin = -100.0;
|
static const _rssiMin = -100.0;
|
||||||
static const _rssiMax = -20.0;
|
static const _rssiMax = -20.0;
|
||||||
|
|
||||||
double _rssi(double d) => rssiRef - 10 * pathLossExp * math.log(d) / math.ln10;
|
double _rssi(double d) =>
|
||||||
|
rssiRef - 10 * pathLossExp * math.log(d) / math.ln10;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
@@ -1200,8 +1198,12 @@ class _ModelCurvePainter extends CustomPainter {
|
|||||||
for (final dist in [1.0, 3.0, 5.0, 10.0]) {
|
for (final dist in [1.0, 3.0, 5.0, 10.0]) {
|
||||||
final t = (dist - _dMin) / (_dMax - _dMin);
|
final t = (dist - _dMin) / (_dMax - _dMin);
|
||||||
final x = padding + t * plotW;
|
final x = padding + t * plotW;
|
||||||
_drawText(canvas, '${dist.toInt()}m', Offset(x - 8, size.height - 14),
|
_drawText(
|
||||||
labelStyle);
|
canvas,
|
||||||
|
'${dist.toInt()}m',
|
||||||
|
Offset(x - 8, size.height - 14),
|
||||||
|
labelStyle,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user