// lib/editor/input/diagnostic_logger.dart // // On-device input diagnostics. When the user manually enables the diagnostic // (the toolbar toggle), every native pen event (raw button/flag/tilt fields) // and every zoom frame is emitted through the standard `dart:developer` log // channel (name 'badnote.input') — capturable via `flutter run`, DevTools, or // any log tool — AND mirrored to a text file as a fallback for the packaged // GUI build, which has no attached console. Disabled by default (no overhead). import 'dart:async'; import 'dart:developer' as developer; import 'dart:io'; import 'package:path_provider/path_provider.dart'; class DiagnosticLogger { DiagnosticLogger._(); static final DiagnosticLogger instance = DiagnosticLogger._(); final List _buffer = []; File? _file; Timer? _timer; int _epochMs = 0; bool _active = false; bool get isActive => _active; /// Absolute path of the current log file (shown in the overlay), or null. String? path; /// Begin a session. Enables the `dart:developer` log channel immediately and /// opens the fallback file (best-effort). Safe to call repeatedly. Future start() async { if (_active) return; _active = true; // developer.log works even if the file can't be opened _epochMs = DateTime.now().millisecondsSinceEpoch; developer.log('--- session start ${DateTime.now().toIso8601String()} ---', name: 'badnote.input'); try { Directory dir; try { dir = await getApplicationDocumentsDirectory(); } catch (_) { dir = await getTemporaryDirectory(); } final file = File('${dir.path}${Platform.pathSeparator}badnote_input_log.txt'); await file.writeAsString( '# BadNote input diagnostic log\n' '# started ${DateTime.now().toIso8601String()}\n' '# columns: \n', flush: true, ); _file = file; path = file.path; _buffer.clear(); _timer = Timer.periodic(const Duration(seconds: 1), (_) => _flush()); } catch (_) { // File is a fallback; never break the app over it. } } /// Emit one diagnostic line through the standard log channel and the file. void log(String line) { if (!_active) return; developer.log(line, name: 'badnote.input'); if (_file == null) return; final t = DateTime.now().millisecondsSinceEpoch - _epochMs; _buffer.add('$t $line'); if (_buffer.length >= 1000) _flush(); } Future _flush() async { final file = _file; if (file == null || _buffer.isEmpty) return; final chunk = '${_buffer.join('\n')}\n'; _buffer.clear(); try { await file.writeAsString(chunk, mode: FileMode.append, flush: true); } catch (_) {} } /// Flush and stop. The file remains on disk for retrieval. Future stop() async { if (!_active) return; _active = false; _timer?.cancel(); _timer = null; await _flush(); } }