summaryrefslogtreecommitdiff
path: root/lib/logs.dart
diff options
context:
space:
mode:
Diffstat (limited to 'lib/logs.dart')
-rwxr-xr-xlib/logs.dart242
1 files changed, 242 insertions, 0 deletions
diff --git a/lib/logs.dart b/lib/logs.dart
new file mode 100755
index 0000000..78f43c2
--- /dev/null
+++ b/lib/logs.dart
@@ -0,0 +1,242 @@
+import 'package:flutter/material.dart';
+import 'package:multiselect/multiselect.dart';
+
+enum LogType {
+ error,
+ warning,
+ info,
+ debug,
+ trace,
+}
+
+extension LogTypeExtension on LogType {
+ static LogType parse(String logType) {
+ final logTypeS = logType.toLowerCase().trim();
+ switch (logTypeS) {
+ case 'error':
+ return LogType.error;
+ case 'warning':
+ return LogType.warning;
+ case 'info':
+ return LogType.info;
+ case 'debug':
+ return LogType.debug;
+ case 'trace':
+ return LogType.trace;
+ default:
+ // there's no particular reason why this should be the default
+ // but hopefully this line is never needed
+ return LogType.error;
+ }
+ }
+
+ String name() {
+ switch (this) {
+ case LogType.error:
+ return "Error";
+ case LogType.warning:
+ return "Warning";
+ case LogType.info:
+ return "Info";
+ case LogType.debug:
+ return "Debug";
+ case LogType.trace:
+ return "Trace";
+ }
+ }
+}
+
+class LogEntry {
+ final bool scriptLog;
+ final LogType logType;
+ final String? file;
+ final int? lineNumber;
+ final String message;
+ final DateTime time;
+
+ LogEntry(
+ this.scriptLog,
+ this.logType,
+ this.file,
+ this.lineNumber,
+ this.message,
+ ) : this.time = DateTime.now();
+}
+
+class _LogFilter extends StatelessWidget {
+ const _LogFilter(this.label, this.onChanged, this.selectedValues);
+
+ final String label;
+ final Function(List<String>) onChanged;
+ final List<LogType> selectedValues;
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ children: [
+ Text(
+ this.label,
+ style: const TextStyle(fontSize: 16),
+ ),
+ const SizedBox(width: 10),
+ SizedBox(
+ width: 200,
+ height: 40,
+ child: DropDownMultiSelect(
+ options: LogType.values.map((e) => e.name()).toList(),
+ selectedValues: this.selectedValues.map((e) => e.name()).toList(),
+ whenEmpty: "Filter Script Logs",
+ isDense: true,
+ onChanged: this.onChanged,
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class _LogLine extends StatelessWidget {
+ const _LogLine(this.entry);
+
+ final LogEntry entry;
+
+ @override
+ Widget build(BuildContext context) {
+ Map<LogType, Widget> icons = {
+ LogType.error: Icon(
+ Icons.error,
+ color: Colors.red[300],
+ size: 24,
+ ),
+ LogType.warning: Icon(
+ Icons.warning,
+ color: Colors.yellow[300],
+ size: 24,
+ ),
+ LogType.info: Icon(
+ Icons.info,
+ color: Colors.blue[300],
+ size: 24,
+ ),
+ LogType.debug: Icon(
+ Icons.bug_report,
+ color: Colors.green[300],
+ size: 24,
+ ),
+ LogType.trace: Icon(
+ Icons.message,
+ color: Colors.grey[300],
+ size: 24,
+ ),
+ };
+
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 3),
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ color: Colors.purple.shade900.withAlpha(128),
+ border: Border.all(color: Colors.purple.shade800, width: 3),
+ borderRadius: const BorderRadius.all(Radius.circular(4)),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(6),
+ child: Row(
+ children: [
+ icons[this.entry.logType]!,
+ const SizedBox(width: 5),
+ SizedBox(width: 70, child: Text(this.entry.logType.name())),
+ SizedBox(width: 54, child: Text(this.entry.scriptLog ? 'Script' : 'Runtime')),
+ const SizedBox(width: 20),
+ Expanded(
+ child: Text(
+ this.entry.message,
+ style: const TextStyle(fontFamily: 'Consolas'),
+ ),
+ ),
+ const SizedBox(width: 20),
+ Text("${this.entry.file}:${this.entry.lineNumber}"),
+ const SizedBox(width: 10),
+ Text(
+ "${this.entry.time.hour.toString().padLeft(2, '0')}:${this.entry.time.minute.toString().padLeft(2, '0')}")
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class LogPage extends StatefulWidget {
+ const LogPage(this.logEntries, {super.key});
+
+ final List<LogEntry> logEntries;
+
+ @override
+ State<StatefulWidget> createState() => _LogPageState();
+}
+
+class _LogPageState extends State<LogPage> {
+ Set<LogType> runtimeLogsFilter = {LogType.error, LogType.warning};
+ Set<LogType> scriptLogsFilter = Set.from(LogType.values);
+ String searchFilter = '';
+
+ Function(List<String>) _generateOnFilter(Set<LogType> filter) {
+ return (values) {
+ setState(() {
+ filter.clear();
+ filter.addAll(values.map((e) => LogTypeExtension.parse(e)));
+ });
+ };
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Center(
+ child: Padding(
+ padding: const EdgeInsets.all(20),
+ child: Column(children: [
+ Row(children: [
+ Expanded(
+ child: SizedBox(
+ height: 40,
+ child: TextField(
+ autocorrect: false,
+ onChanged: (search) => setState(() => this.searchFilter = search),
+ decoration:
+ const InputDecoration(border: OutlineInputBorder(), labelText: 'Search'),
+ ),
+ ),
+ ),
+ const SizedBox(width: 80),
+ _LogFilter(
+ "Runtime Logs Filter:",
+ _generateOnFilter(this.runtimeLogsFilter),
+ runtimeLogsFilter.toList(),
+ ),
+ const SizedBox(width: 80),
+ _LogFilter(
+ "Script Logs Filter:",
+ _generateOnFilter(this.scriptLogsFilter),
+ scriptLogsFilter.toList(),
+ ),
+ ]),
+ Expanded(
+ child: SingleChildScrollView(
+ reverse: true,
+ child: Column(
+ children: this
+ .widget
+ .logEntries
+ .where((e) =>
+ ((e.scriptLog && this.scriptLogsFilter.contains(e.logType)) ||
+ (!e.scriptLog && this.runtimeLogsFilter.contains(e.logType))) &&
+ (this.searchFilter.isEmpty || e.message.contains(this.searchFilter)))
+ .map((e) => _LogLine(e))
+ .toList()),
+ ),
+ ),
+ ]),
+ ),
+ );
+ }
+}