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 _entries = []; /// Newest-last, read-only view for the UI. List 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'); }