From ae9ef1603aa3b6f661f53524141763aa325a8114 Mon Sep 17 00:00:00 2001 From: zypherift Date: Mon, 13 Oct 2025 00:08:36 +0200 Subject: [PATCH] added loading spinner, fade out, and matched the color theme to the login page --- firka/lib/ui/phone/widgets/login_webview.dart | 147 +++++++++++++----- firka/vendor/fmb_dart | 1 + 2 files changed, 111 insertions(+), 37 deletions(-) create mode 160000 firka/vendor/fmb_dart diff --git a/firka/lib/ui/phone/widgets/login_webview.dart b/firka/lib/ui/phone/widgets/login_webview.dart index 0b9626dd..370fdbbd 100644 --- a/firka/lib/ui/phone/widgets/login_webview.dart +++ b/firka/lib/ui/phone/widgets/login_webview.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:firka/helpers/db/models/app_settings_model.dart'; @@ -16,6 +17,7 @@ import '../../../helpers/db/models/token_model.dart'; import '../../../helpers/firka_bundle.dart'; import '../../../helpers/firka_state.dart'; import '../../../helpers/settings.dart'; +import '../../../ui/model/style.dart'; import '../pages/error/error_page.dart'; class LoginWebviewWidget extends StatefulWidget { @@ -30,13 +32,25 @@ class LoginWebviewWidget extends StatefulWidget { State createState() => _LoginWebviewWidgetState(); } -class _LoginWebviewWidgetState extends FirkaState { +class _LoginWebviewWidgetState extends FirkaState + with TickerProviderStateMixin { late WebViewController _webViewController; + bool _isLoading = true; + AnimationController? _fadeAnimationController; + Animation? _fadeAnimation; @override void initState() { super.initState(); + _fadeAnimationController = AnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ); + + _fadeAnimation = + Tween(begin: 1.0, end: 0.0).animate(_fadeAnimationController!); + var loginUrl = KretaEndpoints.kretaLoginUrl; if (widget.username != null && widget.schoolId != null) { @@ -50,6 +64,24 @@ class _LoginWebviewWidgetState extends FirkaState { ..setJavaScriptMode(JavaScriptMode.unrestricted) ..loadRequest(Uri.parse(loginUrl)) ..setNavigationDelegate(NavigationDelegate( + onPageFinished: (String url) { + Timer(const Duration(milliseconds: 500), () { + if (mounted) { + setState(() { + _isLoading = false; + }); + _fadeAnimationController?.forward().then((_) { + _fadeAnimationController?.reset(); + }); + } + }); + }, + onPageStarted: (String url) { + setState(() { + _isLoading = true; + }); + _fadeAnimationController?.reset(); + }, onNavigationRequest: (NavigationRequest request) async { var uri = Uri.parse(request.url); @@ -137,48 +169,89 @@ class _LoginWebviewWidgetState extends FirkaState { })); } + @override + void dispose() { + _fadeAnimationController?.dispose(); + super.dispose(); + } + + @override Widget build(BuildContext context) { - return Padding( - padding: - EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), - child: FractionallySizedBox( - heightFactor: 0.90, - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Container( - decoration: const BoxDecoration( - color: Color(0xFFB9C8E5), - borderRadius: BorderRadius.all(Radius.circular(2)), + return Material( + color: appStyle.colors.card, + child: Padding( + padding: + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: FractionallySizedBox( + heightFactor: 0.90, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Container( + decoration: BoxDecoration( + color: appStyle.colors.secondary.withValues(alpha: 0.5), + borderRadius: BorderRadius.all(Radius.circular(2)), + ), + width: 40, + height: 4, ), - width: 40, - height: 4, + ), + ], + ), + Container( + height: MediaQuery.of(context).size.height * 0.8, + // Adjust height for content + margin: const EdgeInsets.symmetric(horizontal: 16), + // Add ClipRRect for circular edges + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: Stack( + children: [ + WebViewWidget( + controller: _webViewController, + ), + if (_fadeAnimationController != null && _fadeAnimation != null) + AnimatedBuilder( + animation: _fadeAnimationController!, + builder: (context, child) => AnimatedOpacity( + opacity: _isLoading + ? 1.0 + : _fadeAnimationController!.isAnimating + ? _fadeAnimation!.value + : 0.0, + duration: const Duration(milliseconds: 500), + child: Container( + color: appStyle.colors.background, + child: Center( + child: SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator( + strokeWidth: 3, + valueColor: AlwaysStoppedAnimation( + appStyle.colors.accent, + ), + ), + ), + ), + ), + ), + ), + ], ), ), - ], - ), - Container( - height: MediaQuery.of(context).size.height * 0.8, - // Adjust height for content - margin: const EdgeInsets.symmetric(horizontal: 16), - // Add ClipRRect for circular edges - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: WebViewWidget( - controller: _webViewController, - ), ), - ), - ], + ], + ), ), ), ), diff --git a/firka/vendor/fmb_dart b/firka/vendor/fmb_dart new file mode 160000 index 00000000..fb711c8c --- /dev/null +++ b/firka/vendor/fmb_dart @@ -0,0 +1 @@ +Subproject commit fb711c8c407b0efc326516829b8a5ede83321dc2