diff --git a/lib/dialogs/error_dialog.dart b/lib/dialogs/error_dialog.dart new file mode 100644 index 0000000000000000000000000000000000000000..083491c1afc5ba120a2e865e06f67fca07204e33 --- /dev/null +++ b/lib/dialogs/error_dialog.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class ErrorDialog extends StatelessWidget { + final String title; + final Object? error; + + const ErrorDialog({super.key, required this.title, required this.error}); + + @override + Widget build(BuildContext context) { + return SimpleDialog( + title: Text(title), + children: [ + SimpleDialogOption( + child: Text('An error occurred: ${error.runtimeType}\n$error'), + ), + ], + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 9fd493976f7f5858fc4c9896d87f8612edff55fd..3fd81e6e3263b4feb9eaeeb8b0068848402fe833 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'branding.dart'; import 'context.dart'; +import 'dialogs/error_dialog.dart'; import 'drawer/drawer.dart'; import 'timetable/timetable.dart'; import 'toolbar/toolbar.dart'; @@ -63,7 +65,9 @@ class _MyHomePageState extends State<MyHomePage> { @override void initState() { super.initState(); - loadProfile(); + loadProfile().onError((error, _) { + _showErrorDialog('Failed to connect', error); + }); } @override @@ -74,6 +78,14 @@ class _MyHomePageState extends State<MyHomePage> { super.dispose(); } + void _showErrorDialog(String title, Object? error) { + stderr.write(error); + showDialog( + context: context, + builder: (context) => ErrorDialog(title: title, error: error), + ); + } + Future<void> loadProfile() async { final profile = await appContext.profile.firstNotNull; final classes = await untis.allClasses.fetched; diff --git a/lib/untis/fetch.dart b/lib/untis/fetch.dart index e993038985e1d6e5fc065ecf82de83a2009148f6..793fc15dae31f1c1e7af85dd48f728f8caee8b30 100644 --- a/lib/untis/fetch.dart +++ b/lib/untis/fetch.dart @@ -84,14 +84,21 @@ class UntisFetch { final responseString = await utf8.decodeStream(response); final onDownloadEnd = DateTime.now(); - final bodyJson = jsonDecode(responseString); + if (response.statusCode != 200) { + throw FetchError(response.statusCode, responseString); + } - final onDecodeEnd = DateTime.now(); + try { + final bodyJson = jsonDecode(responseString); + final onDecodeEnd = DateTime.now(); - final stats = - FetchStats(onStart, onDownloadStart, onDownloadEnd, onDecodeEnd); + final stats = + FetchStats(onStart, onDownloadStart, onDownloadEnd, onDecodeEnd); - return (bodyJson as Json, resultCookies, stats); + return (bodyJson as Json, resultCookies, stats); + } on FormatException catch (_) { + throw FetchError(response.statusCode, responseString); + } } Future<Json> requestOptions(RequestOptions options) async { @@ -148,3 +155,13 @@ class FetchStats { .join(', '); } } + +class FetchError extends Error { + final int statusCode; + final String body; + + FetchError(this.statusCode, this.body); + + @override + String toString() => 'Server responded with status code $statusCode\n$body'; +}