diff --git a/lib/api.dart b/lib/api.dart index c900c99..872d8b3 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; // For cache storage +import 'package:sheetless/utility.dart'; import 'sheet.dart'; @@ -15,7 +16,7 @@ class ApiClient { ApiClient({required this.baseUrl, this.token}); - Future login(String username, String password) async { + Future> login(String username, String password) async { log.info("Logging in..."); try { final url = '$baseUrl/login'; @@ -28,14 +29,19 @@ class ApiClient { if (response.statusCode == 200) { token = jsonDecode(response.body); log.info('Login successful'); - return true; + return Result.ok(null); } else { log.warning('Login failed: ${response.statusCode}, ${response.body}'); + return Result.error( + Exception( + "Response code ${response.statusCode}\nResponse: ${response.body}", + ), + ); } - } catch (e) { + } on Exception catch (e) { log.warning('Error during login', e); + return Result.error(e); } - return false; } void logout() { diff --git a/lib/login_page.dart b/lib/login_page.dart index 34db564..7444b81 100644 --- a/lib/login_page.dart +++ b/lib/login_page.dart @@ -3,6 +3,7 @@ import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:sheetless/api.dart'; import 'package:sheetless/home_page.dart'; import 'package:sheetless/storage_helper.dart'; +import 'package:sheetless/utility.dart'; class LoginPage extends StatefulWidget { const LoginPage({super.key}); @@ -62,17 +63,16 @@ class _LoginPageState extends State { }); serverUrl = "$serverUrl/api"; final apiClient = ApiClient(baseUrl: serverUrl); - final loginSuccessful = await apiClient.login(email, password); - if (loginSuccessful) { + final loginResult = await apiClient.login(email, password); + if (loginResult.isOk()) { await _storageHelper.writeSecure(SecureStorageKey.url, serverUrl); await _storageHelper.writeSecure(SecureStorageKey.jwt, apiClient.token!); await _storageHelper.writeSecure(SecureStorageKey.email, email); await _storageHelper.writeSecure(SecureStorageKey.password, password); _navigateToMainPage(); } else { - // TODO: give more varied error messages setState(() { - _error = "Login failed."; + _error = "Login failed.\n${loginResult.error()}"; }); } } diff --git a/lib/utility.dart b/lib/utility.dart new file mode 100644 index 0000000..b44d7dd --- /dev/null +++ b/lib/utility.dart @@ -0,0 +1,70 @@ +/// 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 [Err] 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) = Err._; + + bool isOk() { + return this is Ok; + } + + T value() { + Ok ok = this as Ok; + return ok._value; + } + + bool isErr() { + return this is Err; + } + + Exception error() { + Err err = this as Err; + return err._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 Err extends Result { + const Err._(this._error); + + /// The resulting error of this result. + final Exception _error; + + @override + String toString() => 'Result<$T>.error($_error)'; +}