kopia lustrzana https://gitlab.com/mysocialportal/relatica
229 wiersze
6.8 KiB
Dart
229 wiersze
6.8 KiB
Dart
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:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:stack_trace/stack_trace.dart';
|
|
|
|
import '../controls/padding.dart';
|
|
import '../controls/responsive_max_width.dart';
|
|
import '../controls/standard_appbar.dart';
|
|
import '../riverpod_controllers/log_service.dart';
|
|
import '../riverpod_controllers/settings_services.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 ConsumerStatefulWidget {
|
|
const LogViewerScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<LogViewerScreen> createState() => _LogViewerScreenState();
|
|
}
|
|
|
|
class _LogViewerScreenState extends ConsumerState<LogViewerScreen> {
|
|
var filterText = '';
|
|
var filterByText = false;
|
|
var filterByModule = false;
|
|
var filterModuleName = '';
|
|
var attemptingWrite = false;
|
|
|
|
Future<void> _writeEventsLog(List<LogRecord> 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',
|
|
Trace.current(),
|
|
);
|
|
if (mounted) {
|
|
buildSnackbar(context, 'Error attempting to write out log: $e');
|
|
}
|
|
}
|
|
attemptingWrite = false;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final logLevel = ref.watch(logLevelSettingProvider);
|
|
final events = ref.watch(logHistoryProvider);
|
|
|
|
return Scaffold(
|
|
appBar: StandardAppBar.build(context, 'Log Viewer'),
|
|
body: Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: ResponsiveMaxWidth(
|
|
child: Column(
|
|
children: [
|
|
buildLogPanel(logLevel),
|
|
buildModuleFilter(events),
|
|
buildTextSearchPanel(),
|
|
const VerticalPadding(),
|
|
buildLogList(logLevel, events),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
floatingActionButton: FloatingActionButton.small(
|
|
onPressed: () => _writeEventsLog(events),
|
|
child: const Icon(Icons.save),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildLogPanel(Level logLevel) {
|
|
return ListTile(
|
|
title: const Text('Log Level'),
|
|
trailing: DropdownButton<Level>(
|
|
value: logLevel,
|
|
items: Level.LEVELS
|
|
.map((c) => DropdownMenuItem(value: c, child: Text(c.name)))
|
|
.toList(),
|
|
onChanged: (value) {
|
|
ref.read(logLevelSettingProvider.notifier).value =
|
|
value ?? Level.OFF;
|
|
}),
|
|
);
|
|
}
|
|
|
|
Widget buildModuleFilter(List<LogRecord> events) {
|
|
final modules = events.map((e) => e.loggerName).toSet().toList();
|
|
modules.sort();
|
|
modules.add('');
|
|
return ListTile(
|
|
leading: Checkbox(
|
|
value: filterByModule,
|
|
onChanged: (bool? value) {
|
|
setState(() {
|
|
filterByModule = value ?? false;
|
|
});
|
|
},
|
|
),
|
|
title: filterByModule ? null : const Text('Filter by module'),
|
|
trailing: !filterByModule
|
|
? null
|
|
: DropdownButton<String>(
|
|
value: filterModuleName,
|
|
items: modules
|
|
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
|
|
.toList(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
filterModuleName = value ?? '';
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
|
|
Widget buildTextSearchPanel() {
|
|
return ListTile(
|
|
leading: Checkbox(
|
|
value: filterByText,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
filterByText = value ?? false;
|
|
});
|
|
},
|
|
),
|
|
title: TextField(
|
|
enabled: filterByText,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
filterText = value.toLowerCase();
|
|
});
|
|
},
|
|
decoration: InputDecoration(
|
|
labelText: 'Filter by text',
|
|
alignLabelWithHint: true,
|
|
border: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color: Theme.of(context).highlightColor,
|
|
),
|
|
borderRadius: BorderRadius.circular(5.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildLogList(Level logLevel, List<LogRecord> allEvents) {
|
|
final events = allEvents.where((e) {
|
|
final levelFilterPasses = e.level >= logLevel;
|
|
final passesTextFilter = filterByText
|
|
? e.message.toLowerCase().contains(filterText.toLowerCase())
|
|
: true;
|
|
final passesModuleFilter = filterByModule
|
|
? filterModuleName.isEmpty || e.loggerName == filterModuleName
|
|
: true;
|
|
|
|
return levelFilterPasses && passesTextFilter && passesModuleFilter;
|
|
}).toList();
|
|
return Expanded(
|
|
child: ListView.separated(
|
|
itemBuilder: (context, index) {
|
|
final event = events[index];
|
|
return ListTile(
|
|
onLongPress: () {
|
|
copyToClipboard(
|
|
context: context,
|
|
text: jsonEncode(event.toJson()),
|
|
);
|
|
},
|
|
titleAlignment: ListTileTitleAlignment.titleHeight,
|
|
leading: Text(event.level.toString()),
|
|
title:
|
|
Text('${event.loggerName} at ${event.time.toIso8601String()}'),
|
|
subtitle: Text(
|
|
event.message,
|
|
softWrap: true,
|
|
),
|
|
);
|
|
},
|
|
separatorBuilder: (_, __) => const Divider(),
|
|
itemCount: events.length,
|
|
),
|
|
);
|
|
}
|
|
}
|