From 1aa54be199c041d1d50417e30ad545cb30b3d53d Mon Sep 17 00:00:00 2001 From: Armand <4831c0@proton.me> Date: Tue, 26 Aug 2025 22:00:48 +0200 Subject: [PATCH] add warning and button to reauth --- .../lib/helpers/api/client/kreta_client.dart | 13 +++- firka/lib/helpers/api/exceptions/token.dart | 7 ++ firka/lib/helpers/api/token_grant.dart | 5 +- firka/lib/helpers/ui/firka_card.dart | 78 +++++++++---------- firka/lib/l10n | 2 +- firka/lib/main.dart | 1 + firka/lib/ui/phone/pages/extras/extras.dart | 26 ++++++- .../ui/phone/screens/home/home_screen.dart | 40 +++++++--- .../lib/ui/phone/widgets/bottom_nav_icon.dart | 6 +- 9 files changed, 116 insertions(+), 62 deletions(-) create mode 100644 firka/lib/helpers/api/exceptions/token.dart diff --git a/firka/lib/helpers/api/client/kreta_client.dart b/firka/lib/helpers/api/client/kreta_client.dart index 720a8b9..8b3389b 100644 --- a/firka/lib/helpers/api/client/kreta_client.dart +++ b/firka/lib/helpers/api/client/kreta_client.dart @@ -15,6 +15,7 @@ import '../../db/models/token_model.dart'; import '../../db/util.dart'; import '../../debug_helper.dart'; import '../consts.dart'; +import '../exceptions/token.dart'; import '../model/grade.dart'; import '../model/notice_board.dart'; import '../model/omission.dart'; @@ -131,7 +132,9 @@ class KretaClient { } } } catch (ex) { - if (ex is! DioException || counter >= backoffCount) { + if (_isTokenExpired(ex) || + ex is! DioException || + counter >= backoffCount) { rethrow; } @@ -296,7 +299,9 @@ class KretaClient { } } } catch (ex) { - if (ex is! DioException || counter >= backoffCount) { + if (_isTokenExpired(ex) || + ex is! DioException || + counter >= backoffCount) { rethrow; } @@ -548,3 +553,7 @@ class KretaClient { omissionsCache = null; } } + +bool _isTokenExpired(Object ex) => + ex.toString() == TokenExpiredException().toString() || + ex.toString() == InvalidGrantException().toString(); diff --git a/firka/lib/helpers/api/exceptions/token.dart b/firka/lib/helpers/api/exceptions/token.dart new file mode 100644 index 0000000..70ccdbb --- /dev/null +++ b/firka/lib/helpers/api/exceptions/token.dart @@ -0,0 +1,7 @@ +class TokenExpiredException implements Exception { + TokenExpiredException(); +} + +class InvalidGrantException implements Exception { + InvalidGrantException(); +} diff --git a/firka/lib/helpers/api/token_grant.dart b/firka/lib/helpers/api/token_grant.dart index 5bace89..c170752 100644 --- a/firka/lib/helpers/api/token_grant.dart +++ b/firka/lib/helpers/api/token_grant.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:firka/helpers/api/exceptions/token.dart'; import 'package:firka/helpers/api/resp/token_grant.dart'; import 'package:firka/helpers/db/models/token_model.dart'; @@ -60,8 +61,10 @@ Future extendToken(TokenModel model) async { switch (response.statusCode) { case 200: return TokenGrantResponse.fromJson(response.data); + case 400: + throw TokenExpiredException(); case 401: - throw Exception("Invalid grant"); + throw InvalidGrantException(); default: throw Exception( "Failed to get access token, response code: ${response.statusCode}"); diff --git a/firka/lib/helpers/ui/firka_card.dart b/firka/lib/helpers/ui/firka_card.dart index 22199d2..e3de901 100644 --- a/firka/lib/helpers/ui/firka_card.dart +++ b/firka/lib/helpers/ui/firka_card.dart @@ -9,9 +9,15 @@ class FirkaCard extends StatelessWidget { final List? right; final Widget? extra; final Attach? attached; + final Color? color; const FirkaCard( - {required this.left, this.right, this.extra, this.attached, super.key}); + {required this.left, + this.right, + this.extra, + this.attached, + this.color, + super.key}); @override Widget build(BuildContext context) { @@ -23,30 +29,23 @@ class FirkaCard extends StatelessWidget { if (extra != null) { return SizedBox( - width: MediaQuery - .of(context) - .size - .width, + width: MediaQuery.of(context).size.width, child: Card( - color: appStyle.colors.card, + color: color ?? appStyle.colors.card, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular( - attached == Attach.top - ? attachedRounding - : defaultRounding), - topRight: Radius.circular( - attached == Attach.top - ? attachedRounding - : defaultRounding), - bottomLeft: Radius.circular( - attached == Attach.bottom - ? attachedRounding - : defaultRounding), - bottomRight: Radius.circular( - attached == Attach.bottom - ? attachedRounding - : defaultRounding)), + topLeft: Radius.circular(attached == Attach.top + ? attachedRounding + : defaultRounding), + topRight: Radius.circular(attached == Attach.top + ? attachedRounding + : defaultRounding), + bottomLeft: Radius.circular(attached == Attach.bottom + ? attachedRounding + : defaultRounding), + bottomRight: Radius.circular(attached == Attach.bottom + ? attachedRounding + : defaultRounding)), ), child: Padding( padding: const EdgeInsets.all(16.0), @@ -67,30 +66,23 @@ class FirkaCard extends StatelessWidget { ); } else { return SizedBox( - width: MediaQuery - .of(context) - .size - .width, + width: MediaQuery.of(context).size.width, child: Card( - color: appStyle.colors.card, + color: color ?? appStyle.colors.card, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular( - attached == Attach.top - ? attachedRounding - : defaultRounding), - topRight: Radius.circular( - attached == Attach.top - ? attachedRounding - : defaultRounding), - bottomLeft: Radius.circular( - attached == Attach.bottom - ? attachedRounding - : defaultRounding), - bottomRight: Radius.circular( - attached == Attach.bottom - ? attachedRounding - : defaultRounding)), + topLeft: Radius.circular(attached == Attach.top + ? attachedRounding + : defaultRounding), + topRight: Radius.circular(attached == Attach.top + ? attachedRounding + : defaultRounding), + bottomLeft: Radius.circular(attached == Attach.bottom + ? attachedRounding + : defaultRounding), + bottomRight: Radius.circular(attached == Attach.bottom + ? attachedRounding + : defaultRounding)), ), child: Padding( padding: const EdgeInsets.all(16.0), diff --git a/firka/lib/l10n b/firka/lib/l10n index f69c2d2..e62275a 160000 --- a/firka/lib/l10n +++ b/firka/lib/l10n @@ -1 +1 @@ -Subproject commit f69c2d2fe2b161a4e94706ee9a577ece677f3d84 +Subproject commit e62275ae5d5de8e68e39aa8c5e3d1424155fdafd diff --git a/firka/lib/main.dart b/firka/lib/main.dart index 3afd858..724d7ca 100644 --- a/firka/lib/main.dart +++ b/firka/lib/main.dart @@ -189,6 +189,7 @@ Future initializeApp() async { void main() async { dio.options.connectTimeout = Duration(seconds: 5); dio.options.receiveTimeout = Duration(seconds: 3); + dio.options.validateStatus = (status) => status != null && status < 500; runZonedGuarded(() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/firka/lib/ui/phone/pages/extras/extras.dart b/firka/lib/ui/phone/pages/extras/extras.dart index a41ab43..de0a5a1 100644 --- a/firka/lib/ui/phone/pages/extras/extras.dart +++ b/firka/lib/ui/phone/pages/extras/extras.dart @@ -6,8 +6,10 @@ import 'package:flutter/material.dart'; import '../../../../helpers/firka_bundle.dart'; import '../../screens/debug/debug_screen.dart'; +import '../../screens/login/login_screen.dart'; -void showExtrasBottomSheet(BuildContext context, AppInitialization data) { +void showExtrasBottomSheet( + BuildContext context, bool loggedOut, AppInitialization data) { showModalBottomSheet( context: context, elevation: 100, @@ -39,6 +41,24 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { padding: const EdgeInsets.all(16.0), child: Column( children: [ + !loggedOut + ? SizedBox() + : GestureDetector( + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DefaultAssetBundle( + bundle: FirkaBundle(), + child: LoginScreen(data)))); + }, + child: FirkaCard( + left: [Text(data.l10n.reauth_screen)], + right: [], + color: appStyle.colors.accent, + ), + ), GestureDetector( onTap: () => { Navigator.pop(context), @@ -50,7 +70,7 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { child: DebugScreen(data)))) }, child: FirkaCard( - left: [Text('Debug screen')], + left: [Text(data.l10n.debug_screen)], right: [], ), ), @@ -66,7 +86,7 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { data, data.settings.items)))); }, child: FirkaCard( - left: [Text('Settings')], + left: [Text(data.l10n.settings_screen)], right: [], ), ) diff --git a/firka/lib/ui/phone/screens/home/home_screen.dart b/firka/lib/ui/phone/screens/home/home_screen.dart index 8321a2d..9373a98 100644 --- a/firka/lib/ui/phone/screens/home/home_screen.dart +++ b/firka/lib/ui/phone/screens/home/home_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:firka/helpers/api/client/kreta_client.dart'; +import 'package:firka/helpers/api/exceptions/token.dart'; import 'package:firka/main.dart'; import 'package:firka/ui/model/style.dart'; import 'package:firka/ui/phone/pages/home/home_grades.dart'; @@ -61,6 +62,7 @@ class _HomeScreenState extends State { List previousPages = List.empty(growable: true); Widget? toast; + bool userLoggedOut = false; ActiveToastType activeToast = ActiveToastType.none; @@ -80,7 +82,20 @@ class _HomeScreenState extends State { var random = Random(); ApiResponse res = - await widget.data.client.getGrades(forceCache: false); + await widget.data.client.getStudent(forceCache: false); + if (res.statusCode >= 400 || + res.err == TokenExpiredException().toString()) { + setState(() { + userLoggedOut = true; + }); + return; + } + + if (res.err != null) { + throw "await widget.data.client.getStudent\n${res.err!}"; + } + + res = await widget.data.client.getGrades(forceCache: false); if (res.err != null) { throw "await widget.data.client.getGrades\n${res.err!}"; @@ -176,6 +191,7 @@ class _HomeScreenState extends State { _updateSystemUI(); }); + userLoggedOut = false; prefetch(); } @@ -334,15 +350,19 @@ class _HomeScreenState extends State { : appStyle.colors.secondary, appStyle.colors.textPrimary), // More Button - BottomNavIconWidget(() { - HapticFeedback.lightImpact(); - showExtrasBottomSheet(context, widget.data); - }, - false, - Majesticon.globeEarthLine, - widget.data.l10n.other, - appStyle.colors.secondary, - appStyle.colors.textPrimary), + BottomNavIconWidget( + () { + HapticFeedback.lightImpact(); + showExtrasBottomSheet( + context, userLoggedOut, widget.data); + }, + false, + Majesticon.globeEarthLine, + widget.data.l10n.other, + appStyle.colors.secondary, + appStyle.colors.textPrimary, + warn: userLoggedOut, + ), ], ), ), diff --git a/firka/lib/ui/phone/widgets/bottom_nav_icon.dart b/firka/lib/ui/phone/widgets/bottom_nav_icon.dart index 9f6cc8c..ac0895b 100644 --- a/firka/lib/ui/phone/widgets/bottom_nav_icon.dart +++ b/firka/lib/ui/phone/widgets/bottom_nav_icon.dart @@ -12,10 +12,11 @@ class BottomNavIconWidget extends StatelessWidget { final String text; final Color iconColor; final Color textColor; + final bool warn; const BottomNavIconWidget(this.onTap, this.active, this.icon, this.text, this.iconColor, this.textColor, - {super.key}); + {this.warn = false, super.key}); @override Widget build(BuildContext context) { @@ -31,7 +32,8 @@ class BottomNavIconWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ FirkaIconWidget(FirkaIconType.majesticons, icon, - color: iconColor, size: 24) + color: warn ? appStyle.colors.errorAccent : iconColor, + size: 24) .build(context), const SizedBox(height: 4), Text(