kopia lustrzana https://gitlab.com/mysocialportal/relatica
166 wiersze
4.8 KiB
Dart
166 wiersze
4.8 KiB
Dart
![]() |
import 'dart:convert';
|
||
|
|
||
|
import 'package:flutter/material.dart';
|
||
|
import 'package:logging/logging.dart';
|
||
|
import 'package:provider/provider.dart';
|
||
|
|
||
|
import '../controls/padding.dart';
|
||
|
import '../controls/responsive_max_width.dart';
|
||
|
import '../controls/standard_appbar.dart';
|
||
|
import '../services/log_service.dart';
|
||
|
import '../services/setting_service.dart';
|
||
|
import '../utils/clipboard_utils.dart';
|
||
|
import '../utils/logrecord_extensions.dart';
|
||
|
|
||
|
class LogViewerScreen extends StatefulWidget {
|
||
|
const LogViewerScreen({super.key});
|
||
|
|
||
|
@override
|
||
|
State<LogViewerScreen> createState() => _LogViewerScreenState();
|
||
|
}
|
||
|
|
||
|
class _LogViewerScreenState extends State<LogViewerScreen> {
|
||
|
var filterText = '';
|
||
|
var filterByText = false;
|
||
|
var filterByModule = false;
|
||
|
var filterModuleName = '';
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
final settings = context.watch<SettingsService>();
|
||
|
final logService = context.watch<LogService>();
|
||
|
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),
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
));
|
||
|
}
|
||
|
|
||
|
Widget buildLogPanel(SettingsService settings) {
|
||
|
return ListTile(
|
||
|
title: const Text('Log Level'),
|
||
|
trailing: DropdownButton<Level>(
|
||
|
value: settings.logLevel,
|
||
|
items: Level.LEVELS
|
||
|
.map((c) => DropdownMenuItem(value: c, child: Text(c.name)))
|
||
|
.toList(),
|
||
|
onChanged: (value) {
|
||
|
settings.logLevel = value ?? Level.OFF;
|
||
|
}),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
Widget buildModuleFilter(SettingsService settings, 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: 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(
|
||
|
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,
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
}
|