import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:path_provider/path_provider.dart'; /// Persistent protocol log for field debugging without a USB cable. /// /// Every line is kept in a bounded in-memory ring (viewable in-app) and, once /// [init] has run, appended to a file that can be pulled with adb: /// /// adb pull /sdcard/Android/data/dev.teamhydra.openscooter/files/openscooter.log /// /// Before [init], or in tests, only the in-memory ring is used. /// The PIN is never written here; callers mask it first. class ProtocolLog extends ChangeNotifier { ProtocolLog._(); static final ProtocolLog instance = ProtocolLog._(); static const maxLines = 2000; final List _lines = []; File? _file; IOSink? _sink; List get lines => List.unmodifiable(_lines); String? get path => _file?.path; Future init() async { if (_file != null) return; try { Directory? dir; if (Platform.isAndroid) dir = await getExternalStorageDirectory(); dir ??= await getApplicationDocumentsDirectory(); _file = File('${dir.path}/openscooter.log'); _sink = _file!.openWrite(mode: FileMode.append); log('LOG', 'opened ${_file!.path}'); } catch (e) { debugPrint('ProtocolLog: could not open log file: $e'); } } void log(String tag, String message) { final line = '${DateTime.now().toIso8601String()} [$tag] $message'; if (kDebugMode) debugPrint(line); _lines.add(line); if (_lines.length > maxLines) _lines.removeRange(0, _lines.length - maxLines); _sink?.writeln(line); notifyListeners(); } Future clear() async { _lines.clear(); await _sink?.flush(); await _sink?.close(); _sink = null; final f = _file; if (f != null) { try { await f.writeAsString(''); } catch (_) {} _sink = f.openWrite(mode: FileMode.append); } notifyListeners(); } Future flush() async => _sink?.flush(); }