summaryrefslogtreecommitdiff
path: root/lib/logs.dart
blob: 78f43c23a1715854a189e8ae1dc278fc9ce63ddc (plain)
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()),
            ),
          ),
        ]),
      ),
    );
  }
}