From f2c4b07154c94d609407a3249cc6a411d8c45d32 Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Wed, 5 Feb 2025 13:05:50 -0600 Subject: [PATCH] Don't pop an error when there is no logs file (#245) --- lib/screens/SiteLogsScreen.dart | 48 +++++++++++++----------------- lib/services/logs.dart | 31 ++++++++++++++++++++ lib/services/result.dart | 52 +++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 lib/services/logs.dart create mode 100644 lib/services/result.dart diff --git a/lib/screens/SiteLogsScreen.dart b/lib/screens/SiteLogsScreen.dart index c6ae222..ed93465 100644 --- a/lib/screens/SiteLogsScreen.dart +++ b/lib/screens/SiteLogsScreen.dart @@ -5,6 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; import 'package:mobile_nebula/models/Site.dart'; +import 'package:mobile_nebula/services/logs.dart'; +import 'package:mobile_nebula/services/result.dart'; import 'package:mobile_nebula/services/settings.dart'; import 'package:mobile_nebula/services/share.dart'; import 'package:mobile_nebula/services/utils.dart'; @@ -22,14 +24,14 @@ class SiteLogsScreen extends StatefulWidget { } class _SiteLogsScreenState extends State { - String logs = ''; - ScrollController controller = ScrollController(); - RefreshController refreshController = RefreshController(initialRefresh: false); + final ScrollController controller = ScrollController(); + final RefreshController refreshController = RefreshController(initialRefresh: false); + final LogsNotifier logsNotifier = LogsNotifier(); var settings = Settings(); @override void initState() { - loadLogs(); + logsNotifier.loadLogs(logFile: widget.site.logFile); super.initState(); } @@ -49,18 +51,29 @@ class _SiteLogsScreenState extends State { scrollable: SimpleScrollable.both, scrollController: controller, onRefresh: () async { - await loadLogs(); + await logsNotifier.loadLogs(logFile: widget.site.logFile); refreshController.refreshCompleted(); }, onLoading: () async { - await loadLogs(); + await logsNotifier.loadLogs(logFile: widget.site.logFile); refreshController.loadComplete(); }, refreshController: refreshController, child: Container( padding: EdgeInsets.all(5), constraints: logBoxConstraints(context), - child: SelectableText(logs.trim(), style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))), + child: ListenableBuilder( + listenable: logsNotifier, + builder: (context, child) => SelectableText( + switch (logsNotifier.logsResult) { + Ok(:var value) => value.trim(), + Error(:var error) => error is LogsNotFoundException + ? error.error() + : Utils.popError(context, "Error while reading logs.", error.toString()), + null => "", + }, + style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), + )), bottomBar: _buildBottomBar(), ); } @@ -143,27 +156,6 @@ class _SiteLogsScreenState extends State { material: (context, child, platform) => BottomAppBar(child: child)); } - loadLogs() async { - var file = File(widget.site.logFile); - try { - final v = await file.readAsString(); - - setState(() { - logs = v; - }); - } on FileSystemException { - Utils.popError(context, 'Error while reading logs', 'No log file was present'); - } catch (err) { - Utils.popError(context, 'Error while reading logs', err.toString()); - } - } - - deleteLogs() async { - var file = File(widget.site.logFile); - await file.writeAsBytes([]); - await loadLogs(); - } - logBoxConstraints(BuildContext context) { if (settings.logWrap) { return BoxConstraints(maxWidth: MediaQuery.of(context).size.width); diff --git a/lib/services/logs.dart b/lib/services/logs.dart new file mode 100644 index 0000000..2c6723b --- /dev/null +++ b/lib/services/logs.dart @@ -0,0 +1,31 @@ +import 'dart:io'; + +import 'package:flutter/widgets.dart'; +import 'package:mobile_nebula/services/result.dart'; + +class LogsNotFoundException implements Exception { + String error() => 'No logs found. Logs will be available after starting the site for the first time.'; +} + +class LogsNotifier extends ChangeNotifier { + Result? logsResult; + + LogsNotifier(); + + loadLogs({required String logFile}) async { + final file = File(logFile); + try { + logsResult = Result.ok(await file.readAsString()); + notifyListeners(); + } on FileSystemException { + logsResult = Result.error(LogsNotFoundException()); + notifyListeners(); + } on Exception catch (err) { + logsResult = Result.error(err); + notifyListeners(); + } catch (err) { + logsResult = Result.error(Exception(err)); + notifyListeners(); + } + } +} diff --git a/lib/services/result.dart b/lib/services/result.dart new file mode 100644 index 0000000..becd1f7 --- /dev/null +++ b/lib/services/result.dart @@ -0,0 +1,52 @@ +/// Utility class that simplifies handling errors. +/// +/// Return a [Result] from a function to indicate success or failure. +/// +/// A [Result] is either an [Ok] with a value of type [T] +/// or an [Error] with an [Exception]. +/// +/// Use [Result.ok] to create a successful result with a value of type [T]. +/// Use [Result.error] to create an error result with an [Exception]. +/// +/// Evaluate the result using a switch statement: +/// ```dart +/// switch (result) { +/// case Ok(): { +/// print(result.value); +/// } +/// case Error(): { +/// print(result.error); +/// } +/// } +/// ``` +sealed class Result { + const Result(); + + /// Creates a successful [Result], completed with the specified [value]. + const factory Result.ok(T value) = Ok._; + + /// Creates an error [Result], completed with the specified [error]. + const factory Result.error(Exception error) = Error._; +} + +/// A successful [Result] with a returned [value]. +final class Ok extends Result { + const Ok._(this.value); + + /// The returned value of this result. + final T value; + + @override + String toString() => 'Result<$T>.ok($value)'; +} + +/// An error [Result] with a resulting [error]. +final class Error extends Result { + const Error._(this.error); + + /// The resulting error of this result. + final Exception error; + + @override + String toString() => 'Result<$T>.error($error)'; +}