diff --git a/lib/api.dart b/lib/api.dart index 872d8b3..5ec1c46 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -5,7 +5,6 @@ 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'; @@ -16,31 +15,22 @@ 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'; - final response = await http.post( - Uri.parse(url), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({'email': username, 'password': password}), - ); + final url = '$baseUrl/login'; + final response = await http.post( + Uri.parse(url), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'email': username, 'password': password}), + ); - if (response.statusCode == 200) { - token = jsonDecode(response.body); - log.info('Login successful'); - return Result.ok(null); - } else { - log.warning('Login failed: ${response.statusCode}, ${response.body}'); - return Result.error( - Exception( - "Response code ${response.statusCode}\nResponse: ${response.body}", - ), - ); - } - } on Exception catch (e) { - log.warning('Error during login', e); - return Result.error(e); + if (response.statusCode == 200) { + token = jsonDecode(response.body); + log.info('Login successful'); + } else { + throw Exception( + "Failed logging in: Response code ${response.statusCode}\nResponse: ${response.body}", + ); } } @@ -49,27 +39,26 @@ class ApiClient { log.info('Logged out successfully.'); } - Future get(String endpoint, {bool isBinary = false}) async { - try { - final url = '$baseUrl$endpoint'; - final headers = { - 'Authorization': 'Bearer $token', - if (!isBinary) 'Content-Type': 'application/json', - }; + Future get( + String endpoint, { + bool isBinary = false, + bool throwExceptionIfStatusCodeNot200 = false, + }) async { + final url = '$baseUrl$endpoint'; + final headers = { + 'Authorization': 'Bearer $token', + if (!isBinary) 'Content-Type': 'application/json', + }; - final response = await http.get(Uri.parse(url), headers: headers); + final response = await http.get(Uri.parse(url), headers: headers); - if (response.statusCode == 200) { - return response; - } else { - log.warning( - 'GET request failed: ${response.statusCode} ${response.body}', - ); - } - } catch (e) { - log.warning('Error during GET request', e); + if (!throwExceptionIfStatusCodeNot200 || response.statusCode == 200) { + return response; + } else { + throw Exception( + 'GET request failed: ${response.statusCode} ${response.body}', + ); } - return null; } Future post( @@ -130,71 +119,41 @@ class ApiClient { } Future> fetchSheets() async { - try { - final response = await get("/list/sheets"); + final response = await get( + "/list/sheets", + throwExceptionIfStatusCodeNot200: true, + ); - if (response == null) { - return List.empty(); - } - - if (response.statusCode == 200) { - final data = jsonDecode(response.body); - return (data as List) - .map((sheet) => Sheet.fromJson(sheet as Map)) - .toList(); - } else { - log.warning( - 'Failed to fetch sheets with status: ${response.statusCode}', - ); - log.info('Response: ${response.body}'); - } - } catch (e) { - log.warning('Error during fetching sheets', e); - } - - return List.empty(); + final data = jsonDecode(response.body); + return (data as List) + .map((sheet) => Sheet.fromJson(sheet as Map)) + .toList(); } - Future fetchPdfFileData(String sheetUuid) async { - try { - final response = await get('/sheet/pdf/$sheetUuid', isBinary: true); + Future fetchPdfFileData(String sheetUuid) async { + final response = await get( + '/sheet/pdf/$sheetUuid', + isBinary: true, + throwExceptionIfStatusCodeNot200: true, + ); - if (response != null && response.statusCode == 200) { - log.info("PDF downloaded"); - return response.bodyBytes; - } else { - log.warning( - "Failed to fetch PDF from API. Status: ${response?.statusCode}", - ); - } - } catch (e) { - log.warning("Error fetching PDF", e); - } - - return null; + log.info("PDF downloaded"); + return response.bodyBytes; } - Future getPdfFileCached(String sheetUuid) async { - try { - final cacheDir = await getTemporaryDirectory(); - final cachedPdfPath = '${cacheDir.path}/$sheetUuid.pdf'; + Future getPdfFileCached(String sheetUuid) async { + final cacheDir = await getTemporaryDirectory(); + final cachedPdfPath = '${cacheDir.path}/$sheetUuid.pdf'; - final cachedFile = File(cachedPdfPath); - if (await cachedFile.exists()) { - log.info("PDF found in cache: $cachedPdfPath"); - return cachedFile; - } - - final pdfFileData = await fetchPdfFileData(sheetUuid); - await cachedFile.writeAsBytes( - pdfFileData!, - ); // TODO: proper error handling - log.info("PDF cached at: $cachedPdfPath"); + final cachedFile = File(cachedPdfPath); + if (await cachedFile.exists()) { + log.info("PDF found in cache: $cachedPdfPath"); return cachedFile; - } catch (e) { - log.warning("Error fetching PDF", e); } - return null; + final pdfFileData = await fetchPdfFileData(sheetUuid); + await cachedFile.writeAsBytes(pdfFileData); + log.info("PDF cached at: $cachedPdfPath"); + return cachedFile; } } diff --git a/lib/home_page.dart b/lib/home_page.dart index 5e37dc7..d20800b 100644 --- a/lib/home_page.dart +++ b/lib/home_page.dart @@ -106,6 +106,7 @@ class _MyHomePageState extends State { }, ); } else if (snapshot.hasError) { + log.warning("Error loading sheets:", snapshot.error); return Center( child: Text( style: Theme.of( diff --git a/lib/login_page.dart b/lib/login_page.dart index 86228b0..c8754b0 100644 --- a/lib/login_page.dart +++ b/lib/login_page.dart @@ -63,15 +63,16 @@ class _LoginPageState extends State { _error = null; }); final apiClient = ApiClient(baseUrl: "$serverUrl/api"); - final loginResult = await apiClient.login(email, password); - if (loginResult.isOk()) { + try { + await apiClient.login(email, password); + await _storageHelper.writeSecure(SecureStorageKey.url, serverUrl); await _storageHelper.writeSecure(SecureStorageKey.jwt, apiClient.token!); await _storageHelper.writeSecure(SecureStorageKey.email, email); await _navigateToMainPage(); - } else { + } catch (e) { setState(() { - _error = "Login failed.\n${loginResult.error()}"; + _error = "Login failed.\n$e"; }); } } diff --git a/lib/sheet_viewer_page.dart b/lib/sheet_viewer_page.dart index 6ce270d..c8a0901 100644 --- a/lib/sheet_viewer_page.dart +++ b/lib/sheet_viewer_page.dart @@ -60,17 +60,11 @@ class _SheetViewerPageState extends State Future loadPdf() async { if (kIsWeb) { - var data = await widget.apiClient.fetchPdfFileData(widget.sheet.uuid); - if (data == null) { - throw Exception("Failed fetching pdf file"); - } + final data = await widget.apiClient.fetchPdfFileData(widget.sheet.uuid); document = await PdfDocument.openData(data); } else { - var file = await widget.apiClient.getPdfFileCached(widget.sheet.uuid); - if (file == null) { - throw Exception("Failed fetching pdf file"); - } + final file = await widget.apiClient.getPdfFileCached(widget.sheet.uuid); document = await PdfDocument.openFile(file.path); } @@ -181,6 +175,7 @@ class _SheetViewerPageState extends State ], ); } else if (snapshot.hasError) { + log.warning("Error loading pdf:", snapshot.error); return Center( child: Text( style: Theme.of( diff --git a/lib/utility.dart b/lib/utility.dart deleted file mode 100644 index b44d7dd..0000000 --- a/lib/utility.dart +++ /dev/null @@ -1,70 +0,0 @@ -/// 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)'; -}