Add loading state management and error handling in KretenLoginWidget for improved user experience
This commit is contained in:
@@ -39,6 +39,7 @@ class _KretenLoginWidgetState extends State<KretenLoginWidget>
|
||||
late AnimationController _animationController;
|
||||
var loadingPercentage = 0;
|
||||
var currentUrl = '';
|
||||
bool _initialPageLoaded = false;
|
||||
bool _hasFadedIn = false;
|
||||
bool _hasError = false;
|
||||
String _errorMessage = '';
|
||||
@@ -48,6 +49,28 @@ class _KretenLoginWidgetState extends State<KretenLoginWidget>
|
||||
static const _loginUrl =
|
||||
'https://idp.e-kreta.hu/connect/authorize?prompt=login&nonce=wylCrqT4oN6PPgQn2yQB0euKei9nJeZ6_ffJ-VpSKZU&response_type=code&code_challenge_method=S256&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&code_challenge=HByZRRnPGb-Ko_wTI7ibIba1HQ6lor0ws4bcgReuYSQ&redirect_uri=https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect&client_id=kreta-ellenorzo-student-mobile-ios&state=refilc_student_mobile';
|
||||
|
||||
static final Uri _redirectUri = Uri.parse(
|
||||
'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect',
|
||||
);
|
||||
|
||||
bool _isRedirectUri(Uri uri) {
|
||||
return uri.scheme == _redirectUri.scheme &&
|
||||
uri.host == _redirectUri.host &&
|
||||
uri.path == _redirectUri.path;
|
||||
}
|
||||
|
||||
bool _shouldIgnoreError(WebResourceError error) {
|
||||
if (error.isForMainFrame == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final String description = error.description.toLowerCase();
|
||||
return error.errorCode == -999 ||
|
||||
description.contains('cancelled') ||
|
||||
description.contains('canceled') ||
|
||||
description.contains('frame load interrupted');
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -61,84 +84,63 @@ class _KretenLoginWidgetState extends State<KretenLoginWidget>
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onNavigationRequest: (n) async {
|
||||
if (n.url.startsWith('https://mobil.e-kreta.hu')) {
|
||||
setState(() {
|
||||
loadingPercentage = 0;
|
||||
currentUrl = n.url;
|
||||
});
|
||||
|
||||
// final String instituteCode = widget.selectedSchool;
|
||||
// if (!n.url.startsWith(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
List<String> requiredThings = n.url
|
||||
.replaceAll(
|
||||
'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=',
|
||||
'')
|
||||
.replaceAll(
|
||||
'&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&state=refilc_student_mobile&session_state=',
|
||||
':')
|
||||
.split(':');
|
||||
|
||||
String code = requiredThings[0];
|
||||
// String sessionState = requiredThings[1];
|
||||
|
||||
widget.onLogin(code);
|
||||
// Future.delayed(const Duration(milliseconds: 500), () {
|
||||
// Navigator.of(context).pop();
|
||||
// });
|
||||
// Navigator.of(context).pop();
|
||||
|
||||
return NavigationDecision.prevent;
|
||||
} else {
|
||||
return NavigationDecision.navigate;
|
||||
final Uri? uri = Uri.tryParse(n.url);
|
||||
if (uri != null && _isRedirectUri(uri)) {
|
||||
final String? code = uri.queryParameters['code'];
|
||||
if (code != null && code.isNotEmpty) {
|
||||
_timeoutTimer?.cancel();
|
||||
widget.onLogin(code);
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
}
|
||||
|
||||
return NavigationDecision.navigate;
|
||||
},
|
||||
onPageStarted: (url) async {
|
||||
// setState(() {
|
||||
// loadingPercentage = 0;
|
||||
// currentUrl = url;
|
||||
// });
|
||||
if (!mounted) return;
|
||||
|
||||
// // final String instituteCode = widget.selectedSchool;
|
||||
// if (!url.startsWith(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
|
||||
// return;
|
||||
// }
|
||||
setState(() {
|
||||
currentUrl = url;
|
||||
_hasError = false;
|
||||
_errorMessage = '';
|
||||
_hasTimedOut = false;
|
||||
|
||||
// List<String> requiredThings = url
|
||||
// .replaceAll(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=',
|
||||
// '')
|
||||
// .replaceAll(
|
||||
// '&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&state=refilc_student_mobile&session_state=',
|
||||
// ':')
|
||||
// .split(':');
|
||||
if (!_initialPageLoaded) {
|
||||
loadingPercentage = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// String code = requiredThings[0];
|
||||
// // String sessionState = requiredThings[1];
|
||||
|
||||
// widget.onLogin(code);
|
||||
// // Future.delayed(const Duration(milliseconds: 500), () {
|
||||
// // Navigator.of(context).pop();
|
||||
// // });
|
||||
// // Navigator.of(context).pop();
|
||||
_startTimeoutTimer();
|
||||
},
|
||||
onProgress: (progress) {
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
loadingPercentage = progress;
|
||||
});
|
||||
},
|
||||
onPageFinished: (url) {
|
||||
_timeoutTimer?.cancel();
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
currentUrl = url;
|
||||
_initialPageLoaded = true;
|
||||
_hasError = false;
|
||||
_hasTimedOut = false;
|
||||
loadingPercentage = 100;
|
||||
});
|
||||
},
|
||||
onWebResourceError: (error) {
|
||||
if (_shouldIgnoreError(error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_timeoutTimer?.cancel();
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_hasError = true;
|
||||
_errorMessage = error.description;
|
||||
@@ -155,7 +157,7 @@ class _KretenLoginWidgetState extends State<KretenLoginWidget>
|
||||
void _startTimeoutTimer() {
|
||||
_timeoutTimer?.cancel();
|
||||
_timeoutTimer = Timer(const Duration(seconds: 15), () {
|
||||
if (mounted && loadingPercentage < 100 && !_hasError) {
|
||||
if (mounted && !_initialPageLoaded && !_hasError) {
|
||||
setState(() {
|
||||
_hasTimedOut = true;
|
||||
});
|
||||
@@ -168,6 +170,7 @@ class _KretenLoginWidgetState extends State<KretenLoginWidget>
|
||||
_hasError = false;
|
||||
_errorMessage = '';
|
||||
_hasTimedOut = false;
|
||||
_initialPageLoaded = false;
|
||||
loadingPercentage = 0;
|
||||
_hasFadedIn = false;
|
||||
});
|
||||
@@ -244,7 +247,7 @@ class _KretenLoginWidgetState extends State<KretenLoginWidget>
|
||||
}
|
||||
|
||||
// Trigger the fade-in animation only once when loading reaches 100%
|
||||
if (loadingPercentage == 100 && !_hasFadedIn) {
|
||||
if (_initialPageLoaded && !_hasFadedIn) {
|
||||
_animationController.forward(); // Play the animation
|
||||
_hasFadedIn =
|
||||
true; // Set the flag to true, so the animation is not replayed
|
||||
@@ -252,9 +255,8 @@ class _KretenLoginWidgetState extends State<KretenLoginWidget>
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// Webview that will be displayed only when the loading is 100%
|
||||
if (loadingPercentage == 100)
|
||||
FadeTransition(
|
||||
Positioned.fill(
|
||||
child: FadeTransition(
|
||||
opacity: Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
@@ -265,23 +267,28 @@ class _KretenLoginWidgetState extends State<KretenLoginWidget>
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
|
||||
// Show the CircularProgressIndicator while loading is not 100%
|
||||
if (loadingPercentage < 100)
|
||||
Center(
|
||||
child: TweenAnimationBuilder(
|
||||
tween: Tween<double>(begin: 0, end: loadingPercentage / 100.0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, double value, child) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: value, // Smoothly animates the progress
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!_initialPageLoaded)
|
||||
Positioned.fill(
|
||||
child: ColoredBox(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Center(
|
||||
child: TweenAnimationBuilder(
|
||||
tween:
|
||||
Tween<double>(begin: 0, end: loadingPercentage / 100.0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, double value, child) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: value == 0 ? null : value,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user