import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../l10n/app_localizations.dart'; import 'radar_timeline.dart'; /// Play/pause control and scrubber for the radar loop. class TimelineBar extends ConsumerWidget { const TimelineBar({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context); final theme = Theme.of(context); final state = ref.watch(radarTimelineProvider); final timeline = ref.read(radarTimelineProvider.notifier); if (!state.hasFrames) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Text( state.isLoading ? l10n.loading : l10n.radarNoData, style: theme.textTheme.bodySmall, ), ); } final frame = state.currentFrame!; // Frame timestamps are UTC; the reader thinks in local time. final label = DateFormat.Hm().format(frame.timestamp.toLocal()); final lastIndex = state.frameCount - 1; return Padding( padding: const EdgeInsets.fromLTRB(4, 0, 12, 0), child: Row( children: [ IconButton( onPressed: timeline.togglePlayPause, tooltip: state.isPlaying ? l10n.timelinePause : l10n.timelinePlay, icon: Icon( state.isPlaying ? Icons.pause_circle_filled : Icons.play_circle_fill, size: 34, ), color: theme.colorScheme.primary, ), Expanded( child: Semantics( label: l10n.timelineScrubber, value: label, child: Slider( value: state.index.toDouble().clamp(0, lastIndex.toDouble()), max: lastIndex.toDouble(), // One division per frame, so dragging lands on a real frame // rather than interpolating to something that does not exist. divisions: lastIndex > 0 ? lastIndex : null, onChanged: (value) => timeline.seek(value.round()), ), ), ), // Tabular figures so the label does not jitter as the digits change // during playback. Text( label, style: theme.textTheme.titleMedium?.copyWith( fontFeatures: const [FontFeature.tabularFigures()], ), ), ], ), ); } }