offline updates and playhead fix

This commit is contained in:
Forrest 2026-08-16 12:46:30 -04:00
parent 7a199fe4df
commit 6663330260
14 changed files with 1673 additions and 545 deletions

64
lib/debug/log_store.dart Normal file
View file

@ -0,0 +1,64 @@
import 'package:flutter/foundation.dart';
/// Severity of a captured log line, used only to tint it in the Debug tab.
enum LogLevel { info, error }
/// A single captured console line, stamped with the wall-clock time it arrived.
@immutable
class LogEntry {
const LogEntry(this.time, this.text, this.level);
final DateTime time;
final String text;
final LogLevel level;
/// `HH:MM:SS.mmm` — enough resolution to correlate bursts while streaming.
String get timeLabel {
String two(int n) => n.toString().padLeft(2, '0');
return '${two(time.hour)}:${two(time.minute)}:${two(time.second)}'
'.${time.millisecond.toString().padLeft(3, '0')}';
}
}
/// In-memory ring buffer of console/system output, surfaced in the Debug tab so
/// the app's logs can be read (and copied) on-device during beta testing —
/// there's no attached debugger on a TestFlight/sideloaded build.
///
/// A process-wide singleton because the capture hooks (the zone `print`
/// override and `FlutterError.onError`) are installed in `main()`, outside the
/// widget/provider tree. The UI listens via [ListenableBuilder].
class LogStore extends ChangeNotifier {
LogStore._();
static final LogStore instance = LogStore._();
/// Keep the tail bounded so a chatty session can't grow memory without limit.
static const int _maxEntries = 3000;
final List<LogEntry> _entries = <LogEntry>[];
/// Newest-last, read-only view for the UI.
List<LogEntry> get entries => List.unmodifiable(_entries);
int get length => _entries.length;
void add(String text, {LogLevel level = LogLevel.info}) {
// A single print can carry embedded newlines; split so each shows as its
// own row (and the timestamp lines up per line).
final now = DateTime.now();
for (final line in text.split('\n')) {
_entries.add(LogEntry(now, line, level));
}
final overflow = _entries.length - _maxEntries;
if (overflow > 0) _entries.removeRange(0, overflow);
notifyListeners();
}
void clear() {
_entries.clear();
notifyListeners();
}
/// The whole buffer as plain text, for copy-to-clipboard / sharing.
String asText() =>
_entries.map((e) => '${e.timeLabel} ${e.text}').join('\n');
}