From c86c7fca49d6e85e2ba932a2bb747ce8007bd3f0 Mon Sep 17 00:00:00 2001 From: Vusumuzi Khoza Date: Mon, 1 Jun 2026 14:45:42 +0200 Subject: [PATCH] minor login fixes --- Dockerfile | 2 +- Dockerfile-lab | 2 +- .../onboarding/login/login_controller.dart | 40 ++++-- .../onboarding/login/login_screen.dart | 119 +++++++++++------- 4 files changed, 106 insertions(+), 57 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4f8d424..5e41551 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN apt-get install -y curl git unzip # define variables ARG FLUTTER_SDK=/usr/local/flutter -ARG FLUTTER_VERSION=3.35.6 +ARG FLUTTER_VERSION=3.44.0 ARG APP=/app/ #clone flutter diff --git a/Dockerfile-lab b/Dockerfile-lab index 3ab557d..8d04fd7 100644 --- a/Dockerfile-lab +++ b/Dockerfile-lab @@ -7,7 +7,7 @@ RUN apt-get install -y curl git unzip # define variables ARG FLUTTER_SDK=/usr/local/flutter -ARG FLUTTER_VERSION=3.35.6 +ARG FLUTTER_VERSION=3.44.0 ARG APP=/app/ #clone flutter diff --git a/lib/screens/onboarding/login/login_controller.dart b/lib/screens/onboarding/login/login_controller.dart index a9c8670..a1a5a41 100644 --- a/lib/screens/onboarding/login/login_controller.dart +++ b/lib/screens/onboarding/login/login_controller.dart @@ -18,13 +18,29 @@ class LoginController extends ChangeNotifier { final model = LoginScreenModel(); late SharedPreferences prefs; BuildContext context; + bool _disposed = false; LoginController(this.context); - Future> login(String username, String password) async { + @override + void dispose() { + _disposed = true; + super.dispose(); + } + + void _safeNotifyListeners() { + if (!_disposed) { + notifyListeners(); + } + } + + Future> login( + String username, + String password, + ) async { try { model.isLoading = true; - notifyListeners(); + _safeNotifyListeners(); prefs = await SharedPreferences.getInstance(); @@ -35,7 +51,7 @@ class LoginController extends ChangeNotifier { logger.i(response.toString()); model.isLoading = false; - notifyListeners(); + _safeNotifyListeners(); if (response.containsKey('token')) { model.status = 'SUCCESS'; @@ -47,26 +63,32 @@ class LoginController extends ChangeNotifier { prefs.setString('lastName', response['lastName']); prefs.setString('phone', response['phone']); prefs.setString('userId', response['uuid']); - prefs.setString('initials', response['firstName'].substring(0, 1) + response['lastName'].substring(0, 1)); + prefs.setString( + 'initials', + response['firstName'].substring(0, 1) + + response['lastName'].substring(0, 1), + ); return ApiResponse.success(model); } else { model.errorMessage = response['errorMessage'] ?? - "Problem with the transaction. Please try again or contact support"; + "Problem with the operation. Please try again or contact support"; return ApiResponse.failure(model.errorMessage!); } } on DioException catch (e, s) { logger.e(s); model.isLoading = false; - notifyListeners(); - return ApiResponse.failure(e.response?.data['errors'][0] ?? "Unknown error"); + _safeNotifyListeners(); + return ApiResponse.failure( + e.response?.data['errors'][0] ?? "Unknown error", + ); } catch (e, s) { logger.e(s); model.isLoading = false; - notifyListeners(); + _safeNotifyListeners(); return ApiResponse.failure( - "Problem processing that transaction. Please try again or contact support", + "Problem processing that operation. Please try again or contact support", ); } } diff --git a/lib/screens/onboarding/login/login_screen.dart b/lib/screens/onboarding/login/login_screen.dart index 8d64b3a..b1e2570 100644 --- a/lib/screens/onboarding/login/login_screen.dart +++ b/lib/screens/onboarding/login/login_screen.dart @@ -31,6 +31,7 @@ class _LoginScreenState extends State { ]; late LoginController _loginController; + StreamSubscription? _authSubscription; @override void initState() { @@ -38,26 +39,28 @@ class _LoginScreenState extends State { _loginController = LoginController(context); googleSignIn = GoogleSignIn.instance; - if(kIsWeb){ + if (kIsWeb) { googleSignIn.initialize(); - googleSignIn.authenticationEvents - .listen(_handleAuthenticationEvent) - .onError(_handleAuthenticationError); - - }else { - unawaited(googleSignIn.initialize(serverClientId: dotenv.env['CLIENTID'])); + _authSubscription = googleSignIn.authenticationEvents + .handleError(_handleAuthenticationError) + .listen(_handleAuthenticationEvent); + } else { + unawaited( + googleSignIn.initialize(serverClientId: dotenv.env['CLIENTID']), + ); } - } Future signInWithGoogle() async { - if (GoogleSignIn.instance.supportsAuthenticate()){ - unawaited(GoogleSignIn.instance.authenticate().then((value) async { - googleSignIn.authenticationEvents - .listen(_handleAuthenticationEvent) - .onError(_handleAuthenticationError); - - })); + if (GoogleSignIn.instance.supportsAuthenticate()) { + _authSubscription?.cancel(); + unawaited( + GoogleSignIn.instance.authenticate().then((value) async { + _authSubscription = googleSignIn.authenticationEvents + .handleError(_handleAuthenticationError) + .listen(_handleAuthenticationEvent); + }), + ); } } @@ -76,15 +79,19 @@ class _LoginScreenState extends State { } Future _handleAuthenticationEvent( - GoogleSignInAuthenticationEvent event, - ) async { + GoogleSignInAuthenticationEvent event, + ) async { + if (!mounted) return; + // #docregion CheckAuthorization final GoogleSignInAccount? user = // ... - // #enddocregion CheckAuthorization - switch (event) { - GoogleSignInAuthenticationEventSignIn() => event.user, - GoogleSignInAuthenticationEventSignOut() => null, - }; + // #enddocregion CheckAuthorization + switch (event) { + GoogleSignInAuthenticationEventSignIn() => event.user, + GoogleSignInAuthenticationEventSignOut() => null, + }; + + if (user == null || !mounted) return; // Check for existing authorization. // #docregion CheckAuthorization @@ -94,9 +101,29 @@ class _LoginScreenState extends State { // #enddocregion CheckAuthorization print(user); - saveUser(user as GoogleSignInAccount); + saveUser(user); - await _loginController.login(user.email, user.id); + if (!mounted) return; + + final response = await _loginController.login(user.email, user.id); + if (response.isSuccess) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Login successful!"), + behavior: SnackBarBehavior.floating, + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(response.error ?? "Login failed"), + behavior: SnackBarBehavior.floating, + backgroundColor: Colors.deepOrange, + ), + ); + } + if (!mounted) return; context.go('/'); } @@ -106,6 +133,7 @@ class _LoginScreenState extends State { @override void dispose() { + _authSubscription?.cancel(); _loginController.dispose(); super.dispose(); } @@ -120,12 +148,12 @@ class _LoginScreenState extends State { key: _formKey, child: Center( child: LayoutBuilder( - builder: (context, constraints) { - final maxWidth = constraints.maxWidth > ResponsivePolicy.md - ? ResponsivePolicy.md.toDouble() - : constraints.maxWidth; - return SizedBox( - width: maxWidth, + builder: (context, constraints) { + final maxWidth = constraints.maxWidth > ResponsivePolicy.md + ? ResponsivePolicy.md.toDouble() + : constraints.maxWidth; + return SizedBox( + width: maxWidth, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -158,7 +186,9 @@ class _LoginScreenState extends State { // Divider with "or" text Row( children: [ - Expanded(child: Divider(color: Colors.grey.shade300)), + Expanded( + child: Divider(color: Colors.grey.shade300), + ), Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Text( @@ -170,28 +200,30 @@ class _LoginScreenState extends State { textAlign: TextAlign.center, ), ), - Expanded(child: Divider(color: Colors.grey.shade300)), + Expanded( + child: Divider(color: Colors.grey.shade300), + ), ], ), SizedBox(height: 30), // Social Login Buttons - if(GoogleSignIn.instance.supportsAuthenticate()) + if (GoogleSignIn.instance.supportsAuthenticate()) _buildGoogleButton() - else ...[ - if (kIsWeb) - web.renderButton() - ], + else ...[if (kIsWeb) web.renderButton()], SizedBox(height: 40), TextButton( onPressed: () { context.go('/'); }, - child: Text('Continue as guest?', style: TextStyle(fontSize: 14)), + child: Text( + 'Continue as guest?', + style: TextStyle(fontSize: 14), + ), ), ], ), ); - } + }, ), ), ), @@ -200,7 +232,7 @@ class _LoginScreenState extends State { ); } - Widget _buildGoogleButton(){ + Widget _buildGoogleButton() { return Skeletonizer( enabled: _loading, child: Row( @@ -210,11 +242,7 @@ class _LoginScreenState extends State { onPressed: () { signInWithGoogle(); }, - icon: Image.asset( - 'assets/google.png', - width: 20, - height: 20, - ), + icon: Image.asset('assets/google.png', width: 20, height: 20), label: Text('Google', style: TextStyle(fontSize: 16)), style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 16), @@ -230,5 +258,4 @@ class _LoginScreenState extends State { ), ); } - }