6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit 7b727be3

AuthorMikkel Thygesen<mikkelet@gmail.com>
Date2026-02-17 23:17:42 +0100
2085: Implemented redirect after login

Changed files

.../android/app/src/main/AndroidManifest.xml       |  2 +-
 .../authentication/authentication_repository.dart  | 33 +++++++++++-----------
 comwell_key_app/lib/common/const.dart              |  4 ---
 comwell_key_app/lib/data/remote/msal_service.dart  |  8 +++++-
 .../lib/presentation/app/app_cubit.dart            | 17 ++++++-----
 .../lib/presentation/app/app_event_listener.dart   | 15 ++++++++--
 .../screens/login/bloc/login_cubit.dart            | 22 ++++++++++-----
 .../presentation/screens/login/login_route.dart    | 19 +++++++------
 .../presentation/screens/login/login_screen.dart   |  7 ++---
 comwell_key_app/lib/routing/app_router.dart        |  5 ++--
 comwell_key_app/lib/utils/locator.dart             |  3 +-
 comwell_key_app/lib/utils/secure_storage.dart      |  7 +++++
 comwell_key_app/lib/utils/uri_utils.dart           |  5 ++++
 13 files changed, 90 insertions(+), 57 deletions(-)

Diff

diff --git a/comwell_key_app/android/app/src/main/AndroidManifest.xml b/comwell_key_app/android/app/src/main/AndroidManifest.xml
index b5c50699..39c38040 100644
--- a/comwell_key_app/android/app/src/main/AndroidManifest.xml
+++ b/comwell_key_app/android/app/src/main/AndroidManifest.xml
@@ -62,7 +62,7 @@
android:path="/adyenPayment"
android:scheme="adyencheckout" />
</intent-filter>
- <intent-filter android:autoVerify="true">
+ <intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
diff --git a/comwell_key_app/lib/authentication/authentication_repository.dart b/comwell_key_app/lib/authentication/authentication_repository.dart
index 3cc2bdc0..7ee65bda 100644
--- a/comwell_key_app/lib/authentication/authentication_repository.dart
+++ b/comwell_key_app/lib/authentication/authentication_repository.dart
@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:comwell_key_app/data/remote/msal_service.dart';
import 'package:comwell_key_app/database/comwell_db.dart';
import 'package:comwell_key_app/tracking/comwell_tracking.dart';
+import 'package:comwell_key_app/utils/secure_storage.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:msal_auth/msal_auth.dart';
@@ -13,12 +14,14 @@ class AuthenticationRepository {
final ComwellDatabase _database;
final ComwellTracking _comwellTracking;
final MSALService _msalService;
+ final ComwellPreferences _comwellPreferences;
AuthenticationRepository(
this._seosRepository,
this._database,
this._comwellTracking,
this._msalService,
+ this._comwellPreferences,
) {
FirebaseAnalytics.instance.setUserProperty(
name: 'login_status',
@@ -26,28 +29,24 @@ class AuthenticationRepository {
);
}
- Future<bool> isLoggedIn() async {
+ bool get isLoggedIn => _comwellPreferences.isLoggedIn;
+
+ Future<void> logOut() async {
try {
- await _msalService.acquireTokenSilent();
- return true;
+ await _msalService.logout();
+ _comwellTracking.trackEvent('logout');
+ await _seosRepository.terminateEndpoint();
+ await _database.deleteDatabase();
+ await secureStorage.deleteAll();
+ await FirebaseAnalytics.instance.setUserProperty(
+ name: 'login_status',
+ value: 'false',
+ );
} catch (e) {
- print("qqq login error=$e");
- return false;
+ // no op
}
}
- Future<void> logOut() async {
- _comwellTracking.trackEvent('logout');
- await _msalService.logout();
- await _seosRepository.terminateEndpoint();
- await _database.deleteDatabase();
- await secureStorage.deleteAll();
- await FirebaseAnalytics.instance.setUserProperty(
- name: 'login_status',
- value: 'false',
- );
- }
-
Future<void> openAuth(Prompt prompt) async {
await _msalService.openAuth(prompt);
await FirebaseAnalytics.instance.logLogin();
diff --git a/comwell_key_app/lib/common/const.dart b/comwell_key_app/lib/common/const.dart
index 6d503870..289ad63b 100644
--- a/comwell_key_app/lib/common/const.dart
+++ b/comwell_key_app/lib/common/const.dart
@@ -1,7 +1,3 @@
-const refreshToken = 'refresh_token';
-const identifier = 'identifier';
-const correlationId = 'correlation_id';
-const isEndpointSetup = 'isEndpointSetup';
const hasKey = 'hasKey';
const needsScaffold = 'needsScaffold';
const kComwellAppBarHeight = 130.0;
diff --git a/comwell_key_app/lib/data/remote/msal_service.dart b/comwell_key_app/lib/data/remote/msal_service.dart
index 6448c616..68b5da6b 100644
--- a/comwell_key_app/lib/data/remote/msal_service.dart
+++ b/comwell_key_app/lib/data/remote/msal_service.dart
@@ -1,5 +1,6 @@
import 'package:comwell_key_app/.generated/assets/assets.gen.dart';
import 'package:comwell_key_app/utils/env_utils.dart';
+import 'package:comwell_key_app/utils/secure_storage.dart';
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:msal_auth/msal_auth.dart';
@@ -9,9 +10,11 @@ class MSALService {
final authorityUrl = dotenv.ENTRA_ID_AUTHORITY_URL;
final clientId = dotenv.ENTRA_ID_CLIENT_ID;
final redirect = dotenv.ENTRA_ID_REDIRECT_URL;
+
late final SingleAccountPca msAuth;
+ final ComwellPreferences _comwellPreferences;
- MSALService();
+ MSALService(this._comwellPreferences);
Future<void> init() async {
try {
@@ -46,6 +49,7 @@ class MSALService {
customWebViewConfig: const CustomWebViewConfig(),
authority: authorityUrl,
);
+ await _comwellPreferences.setIsLoggedIn(true);
} catch (e) {
await logout();
rethrow;
@@ -57,10 +61,12 @@ class MSALService {
scopes: scopes,
authority: authorityUrl,
);
+ await _comwellPreferences.setIsLoggedIn(true);
return response.accessToken;
}
Future<void> logout() async {
+ await _comwellPreferences.setIsLoggedIn(false);
await msAuth.signOut();
}
}
diff --git a/comwell_key_app/lib/presentation/app/app_cubit.dart b/comwell_key_app/lib/presentation/app/app_cubit.dart
index 5b468e21..23dc1f9f 100644
--- a/comwell_key_app/lib/presentation/app/app_cubit.dart
+++ b/comwell_key_app/lib/presentation/app/app_cubit.dart
@@ -4,21 +4,18 @@ import 'package:app_links/app_links.dart';
import 'package:comwell_key_app/base/base_cubit.dart';
import 'package:comwell_key_app/presentation/app/app_events.dart';
import 'package:comwell_key_app/presentation/app/app_state.dart';
-import 'package:flutter/cupertino.dart';
+import 'package:comwell_key_app/utils/uri_utils.dart';
class AppCubit extends BaseCubit<AppState> {
AppCubit() : super(const AppState()) {
_init();
}
- late final StreamSubscription<Uri> appLinksSubscription;
+ late final StreamSubscription<Uri> _appLinksSubscription;
void _init() {
- appLinksSubscription = AppLinks().uriLinkStream.listen((uri) {
- debugPrint("qqq uri=$uri");
- final scheme = uri.scheme.toLowerCase();
- final isDeeplink = scheme == "https" || scheme == "comwell";
- if (isDeeplink) {
+ _appLinksSubscription = AppLinks().uriLinkStream.listen((uri) async {
+ if (uri.isDeeplink) {
safeEmit(state.copyWith(event: Navigate(uri)));
}
});
@@ -27,4 +24,10 @@ class AppCubit extends BaseCubit<AppState> {
void navigate(Uri uri) {
safeEmit(state.copyWith(event: Navigate(uri)));
}
+
+ @override
+ Future<void> close() async {
+ await _appLinksSubscription.cancel();
+ return super.close();
+ }
}
diff --git a/comwell_key_app/lib/presentation/app/app_event_listener.dart b/comwell_key_app/lib/presentation/app/app_event_listener.dart
index 81cdbeae..d90d5db8 100644
--- a/comwell_key_app/lib/presentation/app/app_event_listener.dart
+++ b/comwell_key_app/lib/presentation/app/app_event_listener.dart
@@ -1,8 +1,11 @@
import 'dart:async';
+import 'package:comwell_key_app/authentication/authentication_repository.dart';
import 'package:comwell_key_app/presentation/app/app_cubit.dart';
import 'package:comwell_key_app/presentation/app/app_events.dart';
import 'package:comwell_key_app/presentation/app/app_state.dart';
+import 'package:comwell_key_app/presentation/screens/login/login_route.dart';
+import 'package:comwell_key_app/utils/locator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -38,8 +41,14 @@ class AppEventListener extends StatelessWidget {
}
Future<void> _pushRoute(BuildContext context, String route) async {
+ final authRepo = locator<AuthenticationRepository>();
await Future<void>.delayed(const Duration(milliseconds: 500)); // UX delay
- if (context.mounted) await context.push(route);
+ if (authRepo.isLoggedIn) {
+ if (context.mounted) await context.push(route);
+ } else {
+ final loginRoute = LoginRoute(redirectAfterLogin: route);
+ if (context.mounted) loginRoute.go(context);
+ }
}
Future<void> _goToRoute(BuildContext context, String route) async {
@@ -50,7 +59,9 @@ class AppEventListener extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocListener<AppCubit, AppState>(
- listenWhen: (prev, curr) => prev.event != curr.event,
+ listenWhen: (prev, curr) {
+ return prev.event != curr.event;
+ },
listener: (context, state) {
final event = state.event;
if (event != null) onEvent(context, event);
diff --git a/comwell_key_app/lib/presentation/screens/login/bloc/login_cubit.dart b/comwell_key_app/lib/presentation/screens/login/bloc/login_cubit.dart
index 61ffc507..cf2602ca 100644
--- a/comwell_key_app/lib/presentation/screens/login/bloc/login_cubit.dart
+++ b/comwell_key_app/lib/presentation/screens/login/bloc/login_cubit.dart
@@ -13,7 +13,10 @@ class LoginCubit extends BaseCubit<LoginState> {
this._appCubit, {
required this.forced,
required this.redirectAfterLogin,
- }) : super(const LoginState());
+ }) : super(const LoginState()) {
+ init();
+ }
+
final AuthenticationRepository _authRepository;
final AppCubit _appCubit;
final bool forced;
@@ -25,8 +28,13 @@ class LoginCubit extends BaseCubit<LoginState> {
}
}
- Future<void> login() async {
- await _authRepository.openAuth(Prompt.login);
+ bool get isLoggedId => _authRepository.isLoggedIn;
+
+ Future<void> login(BuildContext context) async {
+ if (!_authRepository.isLoggedIn) {
+ await _authRepository.openAuth(Prompt.login);
+ }
+ if (context.mounted) redirect(context);
}
Future<void> createAccount() async {
@@ -34,11 +42,11 @@ class LoginCubit extends BaseCubit<LoginState> {
}
void redirect(BuildContext context) async {
- print("qqq deeplink redir=${redirectAfterLogin}");
context.go(AppRoutes.overview);
- await Future<void>.delayed(const Duration(milliseconds: 500));
- if (redirectAfterLogin.isNotEmpty && context.mounted) {
- context.push(redirectAfterLogin);
+ if (redirectAfterLogin.isNotEmpty) {
+ // wait for previous nav to finish
+ await Future<void>.delayed(const Duration(milliseconds: 500));
+ _appCubit.navigate(Uri.parse("comwell://profile"));
}
}
}
diff --git a/comwell_key_app/lib/presentation/screens/login/login_route.dart b/comwell_key_app/lib/presentation/screens/login/login_route.dart
index eaeb9222..5d14597c 100644
--- a/comwell_key_app/lib/presentation/screens/login/login_route.dart
+++ b/comwell_key_app/lib/presentation/screens/login/login_route.dart
@@ -1,8 +1,8 @@
+import 'package:comwell_key_app/presentation/app/app_event_listener.dart';
import 'package:comwell_key_app/utils/locator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
-import 'package:comwell_key_app/presentation/navigation/transitions/slide_in_transition.dart';
import 'package:comwell_key_app/presentation/screens/login/bloc/login_cubit.dart';
import 'package:comwell_key_app/presentation/screens/login/login_screen.dart';
@@ -18,18 +18,19 @@ class LoginRoute extends GoRouteData with $LoginRoute {
LoginRoute({this.forced = false, this.redirectAfterLogin = ""});
@override
- Page<void> buildPage(BuildContext context, GoRouterState state) {
- return SlideInTransition(
- state: state,
- child: BlocProvider(
- create: (context) => LoginCubit(
+ Widget build(BuildContext context, GoRouterState state) {
+ return BlocProvider(
+ key: UniqueKey(),
+ create: (context) {
+ return LoginCubit(
locator.get(),
context.read(),
redirectAfterLogin: redirectAfterLogin,
forced: forced,
- ),
- child: const LoginPage(),
- ),
+ );
+ },
+ lazy: false,
+ child: const AppEventListener(child: LoginPage()),
);
}
}
diff --git a/comwell_key_app/lib/presentation/screens/login/login_screen.dart b/comwell_key_app/lib/presentation/screens/login/login_screen.dart
index 39f258ad..23ca1a04 100644
--- a/comwell_key_app/lib/presentation/screens/login/login_screen.dart
+++ b/comwell_key_app/lib/presentation/screens/login/login_screen.dart
@@ -36,11 +36,8 @@ class LoginPage extends StatelessWidget {
),
const SizedBox(height: 32),
LoginButton(
- onPressed: () async {
- await cubit.login();
- if (context.mounted) {
- cubit.redirect(context);
- }
+ onPressed: () {
+ cubit.login(context);
},
),
const SizedBox(height: 20),
diff --git a/comwell_key_app/lib/routing/app_router.dart b/comwell_key_app/lib/routing/app_router.dart
index b8ff246a..2a7f051a 100644
--- a/comwell_key_app/lib/routing/app_router.dart
+++ b/comwell_key_app/lib/routing/app_router.dart
@@ -45,9 +45,8 @@ final router = GoRouter(
observers: [GoRouterObserver()],
redirect: (context, state) async {
final authRepo = locator<AuthenticationRepository>();
- final isLoggedIn = await authRepo.isLoggedIn();
+ final isLoggedIn = authRepo.isLoggedIn;
final isException = authExceptions.contains(state.matchedLocation);
- print("qqq deeplink matchedLocation=${state.matchedLocation}");
if (state.uri.host == 'share-room') {
final sharingType = state.uri.queryParameters['sharingType'];
@@ -70,7 +69,7 @@ final router = GoRouter(
if (!isLoggedIn) {
if (state.matchedLocation == AppRoutes.login) {
- return LoginRoute().location;
+ return null;
} else {
return LoginRoute(redirectAfterLogin: state.matchedLocation).location;
}
diff --git a/comwell_key_app/lib/utils/locator.dart b/comwell_key_app/lib/utils/locator.dart
index 5fe2538d..37ab6861 100644
--- a/comwell_key_app/lib/utils/locator.dart
+++ b/comwell_key_app/lib/utils/locator.dart
@@ -49,7 +49,7 @@ void setupLocator() {
locator.registerSingleton(ComwellTracking());
registerDatabase();
locator.registerSingleton(SecureStorage());
- locator.registerSingleton(MSALService());
+ locator.registerSingleton(MSALService(locator()));
locator.registerSingleton(SeosMobileKeysPlugin());
locator.registerSingleton(ComwellHttpClient());
locator.registerSingleton(Api());
@@ -70,6 +70,7 @@ void setupLocator() {
locator.get(),
locator.get(),
locator.get(),
+ locator.get(),
),
);
locator.registerFactory(() => BookingDetailsRepository());
diff --git a/comwell_key_app/lib/utils/secure_storage.dart b/comwell_key_app/lib/utils/secure_storage.dart
index 0c35534f..ccdbb29c 100644
--- a/comwell_key_app/lib/utils/secure_storage.dart
+++ b/comwell_key_app/lib/utils/secure_storage.dart
@@ -43,6 +43,12 @@ class ComwellPreferences {
_sharedPreferences = await SharedPreferences.getInstance();
}
+ bool get isLoggedIn => _sharedPreferences.getBool(_keyIsLoggedIn) ?? false;
+
+ Future<void> setIsLoggedIn(bool isLoggedIn) async {
+ await _sharedPreferences.setBool(_keyIsLoggedIn, isLoggedIn);
+ }
+
bool get onboardingHasSeenBluetooth =>
_sharedPreferences.getBool(_keyOnboardingHasSeenBluetooth) ?? false;
@@ -71,4 +77,5 @@ class ComwellPreferences {
static const _keyOnboardingHasSeenBluetooth = "_keyOnboardingHasSeenBluetooth";
static const _keyOnboardingHasSeenNotification = "_keyOnboardingHasSeenNotification";
static const _keyOnboardingHasSeenUsageTracking = "_keyOnboardingHasSeenUsageTracking";
+ static const _keyIsLoggedIn = "_keyIsLoggedIn";
}
diff --git a/comwell_key_app/lib/utils/uri_utils.dart b/comwell_key_app/lib/utils/uri_utils.dart
new file mode 100644
index 00000000..41930b70
--- /dev/null
+++ b/comwell_key_app/lib/utils/uri_utils.dart
@@ -0,0 +1,5 @@
+extension UriUtils on Uri {
+ bool get isDeeplink {
+ return scheme.toLowerCase() == "comwell";
+ }
+}