diff --git a/lib/screens/logviewer_screen.dart b/lib/screens/logviewer_screen.dart index 56af3b4..5260f60 100644 --- a/lib/screens/logviewer_screen.dart +++ b/lib/screens/logviewer_screen.dart @@ -1,7 +1,12 @@ import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_file_dialog/flutter_file_dialog.dart'; import 'package:logging/logging.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import '../controls/padding.dart'; @@ -10,7 +15,12 @@ import '../controls/standard_appbar.dart'; import '../services/log_service.dart'; import '../services/setting_service.dart'; import '../utils/clipboard_utils.dart'; +import '../utils/dateutils.dart'; +import '../utils/json_printer.dart'; import '../utils/logrecord_extensions.dart'; +import '../utils/snackbar_builder.dart'; + +final _logger = Logger('LogViewerScreen'); class LogViewerScreen extends StatefulWidget { const LogViewerScreen({super.key}); @@ -24,6 +34,49 @@ class _LogViewerScreenState extends State { var filterByText = false; var filterByModule = false; var filterModuleName = ''; + var attemptingWrite = false; + + Future _writeEventsLog(List events) async { + if (attemptingWrite) { + return; + } + + attemptingWrite = true; + try { + final json = + PrettyJsonEncoder().convert(events.map((e) => e.toJson()).toList()); + final filename = 'EventsLog_${DateTime.now().toFileNameString()}.json'; + if (Platform.isAndroid || Platform.isIOS) { + final params = SaveFileDialogParams( + data: Uint8List.fromList(json.codeUnits), + fileName: filename, + ); + await FlutterFileDialog.saveFile(params: params); + if (mounted) { + buildSnackbar(context, 'Wrote Events Log to: $filename'); + } + } else { + final appsDir = await getApplicationDocumentsDirectory(); + final location = await FilePicker.platform.saveFile( + dialogTitle: 'Save Events Log', + fileName: filename, + initialDirectory: appsDir.path, + ); + if (location != null) { + await File(location).writeAsString(json); + if (mounted) { + buildSnackbar(context, 'Wrote Events Log to: $location'); + } + } + } + } catch (e) { + _logger.severe('Error attempting to write out log: $e'); + if (mounted) { + buildSnackbar(context, 'Error attempting to write out log: $e'); + } + } + attemptingWrite = false; + } @override Widget build(BuildContext context) { @@ -32,23 +85,28 @@ class _LogViewerScreenState extends State { final events = logService.events; return Scaffold( - appBar: StandardAppBar.build(context, 'Log Viewer'), - body: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ResponsiveMaxWidth( - child: Column( - children: [ - buildLogPanel(settings), - buildModuleFilter(settings, events), - buildTextSearchPanel(), - const VerticalPadding(), - buildLogList(settings.logLevel, events), - ], - ), + appBar: StandardAppBar.build(context, 'Log Viewer'), + body: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ResponsiveMaxWidth( + child: Column( + children: [ + buildLogPanel(settings), + buildModuleFilter(settings, events), + buildTextSearchPanel(), + const VerticalPadding(), + buildLogList(settings.logLevel, events), + ], ), ), - )); + ), + ), + floatingActionButton: FloatingActionButton.small( + onPressed: () => _writeEventsLog(events), + child: const Icon(Icons.save), + ), + ); } Widget buildLogPanel(SettingsService settings) { @@ -78,7 +136,7 @@ class _LogViewerScreenState extends State { }); }, ), - title: const Text('Filter by module:'), + title: filterByModule ? null : const Text('Filter by module'), trailing: !filterByModule ? null : DropdownButton( @@ -105,6 +163,7 @@ class _LogViewerScreenState extends State { }, ), title: TextField( + enabled: filterByText, onChanged: (value) { setState(() { filterText = value.toLowerCase(); diff --git a/lib/utils/dateutils.dart b/lib/utils/dateutils.dart index b2ee82e..92c3cb9 100644 --- a/lib/utils/dateutils.dart +++ b/lib/utils/dateutils.dart @@ -58,3 +58,9 @@ class ElapsedDateUtils { return 'seconds ago'; } } + +const _separator = '_'; + +extension DateTimeExtensions on DateTime { + String toFileNameString() => '$year$month$day$_separator$hour$minute$second'; +}