6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit aa3a1496
Changed files
.../screens/login/bloc/login_state.freezed.dart | 274 +++++++++++++++++++++ .../presentation/screens/login/login_route.g.dart | 70 ++++++ comwell_key_app/lib/data/remote/msal_service.dart | 17 +- comwell_key_app/lib/login/auth.dart | 5 - .../lib/login/components/create_user_button.dart | 33 --- .../lib/login/components/forced_logout_banner.dart | 55 ----- .../lib/login/components/login_button.dart | 34 --- comwell_key_app/lib/login/cubit/login_cubit.dart | 29 --- comwell_key_app/lib/login/cubit/login_state.dart | 7 - comwell_key_app/lib/login/login_page.dart | 56 ----- comwell_key_app/lib/login/login_route.dart | 22 -- .../lib/presentation/app/app_cubit.dart | 4 + .../screens/login/bloc/login_cubit.dart | 44 ++++ .../screens/login/bloc/login_state.dart | 12 + .../login/components/create_user_button.dart | 33 +++ .../login/components/forced_logout_banner.dart | 55 +++++ .../screens/login/components/login_button.dart | 34 +++ .../presentation/screens/login/login_route.dart | 35 +++ .../presentation/screens/login/login_screen.dart | 55 +++++ comwell_key_app/lib/routing/app_router.dart | 15 +- 20 files changed, 638 insertions(+), 251 deletions(-)
Diff
diff --git a/comwell_key_app/lib/.generated/presentation/screens/login/bloc/login_state.freezed.dart b/comwell_key_app/lib/.generated/presentation/screens/login/bloc/login_state.freezed.dart
new file mode 100644
index 00000000..a6f3f076
--- /dev/null
+++ b/comwell_key_app/lib/.generated/presentation/screens/login/bloc/login_state.freezed.dart
@@ -0,0 +1,274 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// coverage:ignore-file
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of '../../../../../presentation/screens/login/bloc/login_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity<T>(T value) => value;
+/// @nodoc
+mixin _$LoginState {
+
+ bool get isLoading; AppError get error;
+/// Create a copy of LoginState
+/// with the given fields replaced by the non-null parameter values.
+@JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+$LoginStateCopyWith<LoginState> get copyWith => _$LoginStateCopyWithImpl<LoginState>(this as LoginState, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is LoginState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.error, error) || other.error == error));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,error);
+
+@override
+String toString() {
+ return 'LoginState(isLoading: $isLoading, error: $error)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class $LoginStateCopyWith<$Res> {
+ factory $LoginStateCopyWith(LoginState value, $Res Function(LoginState) _then) = _$LoginStateCopyWithImpl;
+@useResult
+$Res call({
+ bool isLoading, AppError error
+});
+
+
+
+
+}
+/// @nodoc
+class _$LoginStateCopyWithImpl<$Res>
+ implements $LoginStateCopyWith<$Res> {
+ _$LoginStateCopyWithImpl(this._self, this._then);
+
+ final LoginState _self;
+ final $Res Function(LoginState) _then;
+
+/// Create a copy of LoginState
+/// with the given fields replaced by the non-null parameter values.
+@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? error = null,}) {
+ return _then(_self.copyWith(
+isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,error: null == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
+as AppError,
+ ));
+}
+
+}
+
+
+/// Adds pattern-matching-related methods to [LoginState].
+extension LoginStatePatterns on LoginState {
+/// A variant of `map` that fallback to returning `orElse`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LoginState value)? $default,{required TResult orElse(),}){
+final _that = this;
+switch (_that) {
+case _LoginState() when $default != null:
+return $default(_that);case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// Callbacks receives the raw object, upcasted.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case final Subclass2 value:
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LoginState value) $default,){
+final _that = this;
+switch (_that) {
+case _LoginState():
+return $default(_that);case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `map` that fallback to returning `null`.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case final Subclass value:
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LoginState value)? $default,){
+final _that = this;
+switch (_that) {
+case _LoginState() when $default != null:
+return $default(_that);case _:
+ return null;
+
+}
+}
+/// A variant of `when` that fallback to an `orElse` callback.
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return orElse();
+/// }
+/// ```
+
+@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, AppError error)? $default,{required TResult orElse(),}) {final _that = this;
+switch (_that) {
+case _LoginState() when $default != null:
+return $default(_that.isLoading,_that.error);case _:
+ return orElse();
+
+}
+}
+/// A `switch`-like method, using callbacks.
+///
+/// As opposed to `map`, this offers destructuring.
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case Subclass2(:final field2):
+/// return ...;
+/// }
+/// ```
+
+@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, AppError error) $default,) {final _that = this;
+switch (_that) {
+case _LoginState():
+return $default(_that.isLoading,_that.error);case _:
+ throw StateError('Unexpected subclass');
+
+}
+}
+/// A variant of `when` that fallback to returning `null`
+///
+/// It is equivalent to doing:
+/// ```dart
+/// switch (sealedClass) {
+/// case Subclass(:final field):
+/// return ...;
+/// case _:
+/// return null;
+/// }
+/// ```
+
+@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, AppError error)? $default,) {final _that = this;
+switch (_that) {
+case _LoginState() when $default != null:
+return $default(_that.isLoading,_that.error);case _:
+ return null;
+
+}
+}
+
+}
+
+/// @nodoc
+
+
+class _LoginState implements LoginState {
+ const _LoginState({this.isLoading = false, this.error = AppError.none});
+
+
+@override@JsonKey() final bool isLoading;
+@override@JsonKey() final AppError error;
+
+/// Create a copy of LoginState
+/// with the given fields replaced by the non-null parameter values.
+@override @JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+_$LoginStateCopyWith<_LoginState> get copyWith => __$LoginStateCopyWithImpl<_LoginState>(this, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _LoginState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.error, error) || other.error == error));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,error);
+
+@override
+String toString() {
+ return 'LoginState(isLoading: $isLoading, error: $error)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class _$LoginStateCopyWith<$Res> implements $LoginStateCopyWith<$Res> {
+ factory _$LoginStateCopyWith(_LoginState value, $Res Function(_LoginState) _then) = __$LoginStateCopyWithImpl;
+@override @useResult
+$Res call({
+ bool isLoading, AppError error
+});
+
+
+
+
+}
+/// @nodoc
+class __$LoginStateCopyWithImpl<$Res>
+ implements _$LoginStateCopyWith<$Res> {
+ __$LoginStateCopyWithImpl(this._self, this._then);
+
+ final _LoginState _self;
+ final $Res Function(_LoginState) _then;
+
+/// Create a copy of LoginState
+/// with the given fields replaced by the non-null parameter values.
+@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? error = null,}) {
+ return _then(_LoginState(
+isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
+as bool,error: null == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
+as AppError,
+ ));
+}
+
+
+}
+
+// dart format on
diff --git a/comwell_key_app/lib/.generated/presentation/screens/login/login_route.g.dart b/comwell_key_app/lib/.generated/presentation/screens/login/login_route.g.dart
new file mode 100644
index 00000000..e3bc53cf
--- /dev/null
+++ b/comwell_key_app/lib/.generated/presentation/screens/login/login_route.g.dart
@@ -0,0 +1,70 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of '../../../../presentation/screens/login/login_route.dart';
+
+// **************************************************************************
+// GoRouterGenerator
+// **************************************************************************
+
+List<RouteBase> get $appRoutes => [$loginRoute];
+
+RouteBase get $loginRoute =>
+ GoRouteData.$route(path: '/login', factory: $LoginRoute._fromState);
+
+mixin $LoginRoute on GoRouteData {
+ static LoginRoute _fromState(GoRouterState state) => LoginRoute(
+ forced:
+ _$convertMapValue(
+ 'forced',
+ state.uri.queryParameters,
+ _$boolConverter,
+ ) ??
+ false,
+ redirectAfterLogin: state.uri.queryParameters['redirect-after-login'] ?? "",
+ );
+
+ LoginRoute get _self => this as LoginRoute;
+
+ @override
+ String get location => GoRouteData.$location(
+ '/login',
+ queryParams: {
+ if (_self.forced != false) 'forced': _self.forced.toString(),
+ if (_self.redirectAfterLogin != "")
+ 'redirect-after-login': _self.redirectAfterLogin,
+ },
+ );
+
+ @override
+ void go(BuildContext context) => context.go(location);
+
+ @override
+ Future<T?> push<T>(BuildContext context) => context.push<T>(location);
+
+ @override
+ void pushReplacement(BuildContext context) =>
+ context.pushReplacement(location);
+
+ @override
+ void replace(BuildContext context) => context.replace(location);
+}
+
+T? _$convertMapValue<T>(
+ String key,
+ Map<String, String> map,
+ T? Function(String) converter,
+) {
+ final value = map[key];
+ return value == null ? null : converter(value);
+}
+
+bool _$boolConverter(String value) {
+ switch (value) {
+ case 'true':
+ return true;
+ case 'false':
+ return false;
+ default:
+ throw UnsupportedError('Cannot convert "$value" into a bool.');
+ }
+}
diff --git a/comwell_key_app/lib/data/remote/msal_service.dart b/comwell_key_app/lib/data/remote/msal_service.dart
index 876e5cba..6448c616 100644
--- a/comwell_key_app/lib/data/remote/msal_service.dart
+++ b/comwell_key_app/lib/data/remote/msal_service.dart
@@ -39,12 +39,17 @@ class MSALService {
}
Future<void> openAuth(Prompt prompt) async {
- await msAuth.acquireToken(
- scopes: scopes,
- prompt: prompt,
- customWebViewConfig: const CustomWebViewConfig(),
- authority: authorityUrl,
- );
+ try {
+ await msAuth.acquireToken(
+ scopes: scopes,
+ prompt: prompt,
+ customWebViewConfig: const CustomWebViewConfig(),
+ authority: authorityUrl,
+ );
+ } catch (e) {
+ await logout();
+ rethrow;
+ }
}
Future<String> acquireTokenSilent() async {
diff --git a/comwell_key_app/lib/login/auth.dart b/comwell_key_app/lib/login/auth.dart
deleted file mode 100644
index 9cf08dd5..00000000
--- a/comwell_key_app/lib/login/auth.dart
+++ /dev/null
@@ -1,5 +0,0 @@
-enum Auth {
- login,
- createUser,
- changePassword,
-}
diff --git a/comwell_key_app/lib/login/components/create_user_button.dart b/comwell_key_app/lib/login/components/create_user_button.dart
deleted file mode 100644
index 3349095a..00000000
--- a/comwell_key_app/lib/login/components/create_user_button.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-
-class CreateUserButton extends StatelessWidget {
- final Function onPressed;
-
- const CreateUserButton({
- required this.onPressed,
- super.key,
- });
-
- @override
- Widget build(BuildContext context) {
- return OutlinedButton(
- style: OutlinedButton.styleFrom(
- backgroundColor: Colors.transparent,
- side: const BorderSide(color: Colors.transparent),
- ),
- onPressed: () {
- onPressed();
- },
- child: Text(
- context.strings.create_user_button,
- style: TextStyle(
- color: Theme.of(context).colorScheme.surface,
- fontSize: 16,
- fontWeight: FontWeight.w600,
- backgroundColor: Colors.transparent,
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/login/components/forced_logout_banner.dart b/comwell_key_app/lib/login/components/forced_logout_banner.dart
deleted file mode 100644
index 2918f85e..00000000
--- a/comwell_key_app/lib/login/components/forced_logout_banner.dart
+++ /dev/null
@@ -1,55 +0,0 @@
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-
-class ForcedLogoutBanner extends StatelessWidget {
- const ForcedLogoutBanner({super.key});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- return Padding(
- padding: const EdgeInsets.all(16.0),
- child: Container(
- height: 100,
- width: double.infinity,
- decoration: BoxDecoration(
- color: theme.colorScheme.surface,
- borderRadius: BorderRadius.circular(10),
- ),
- padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
- child: Row(
- children: [
- Container(
- width: 64,
- height: 64,
- decoration: BoxDecoration(
- color: Colors.red.withValues(alpha: 0.1),
- shape: BoxShape.circle,
- ),
- child: const Icon(
- Icons.error_outline,
- color: Colors.red,
- size: 36,
- ),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(context.strings.error_logout,
- style: theme.textTheme.titleMedium!.copyWith(
- color: Colors.red, fontWeight: FontWeight.bold)),
- Text(context.strings.error_logout_subtitle,
- softWrap: true,
- style: theme.textTheme.bodySmall!
- .copyWith(color: Colors.red)),
- ],
- ),
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/login/components/login_button.dart b/comwell_key_app/lib/login/components/login_button.dart
deleted file mode 100644
index 82715cdf..00000000
--- a/comwell_key_app/lib/login/components/login_button.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'package:comwell_key_app/utils/l10n_utils.dart';
-import 'package:flutter/material.dart';
-
-class LoginButton extends StatelessWidget {
- final VoidCallback onPressed;
-
- const LoginButton({
- required this.onPressed,
- super.key,
- });
-
- @override
- Widget build(BuildContext context) {
- return Container(
- margin: const EdgeInsets.symmetric(horizontal: 30),
- child: ElevatedButton(
- onPressed: onPressed,
- style: ElevatedButton.styleFrom(
- minimumSize: const Size(358, 52),
- backgroundColor: Theme.of(context).colorScheme.surface,
- textStyle: TextStyle(
- color: Theme.of(context).colorScheme.onSurface,
- fontSize: 16,
- fontWeight: FontWeight.w600,
- ),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(30),
- ),
- ),
- child: Text(context.strings.login_button),
- ),
- );
- }
-}
diff --git a/comwell_key_app/lib/login/cubit/login_cubit.dart b/comwell_key_app/lib/login/cubit/login_cubit.dart
deleted file mode 100644
index 45429504..00000000
--- a/comwell_key_app/lib/login/cubit/login_cubit.dart
+++ /dev/null
@@ -1,29 +0,0 @@
-import 'package:bloc/bloc.dart';
-import 'package:comwell_key_app/authentication/authentication_repository.dart';
-import 'package:equatable/equatable.dart';
-import 'package:msal_auth/msal_auth.dart';
-
-part 'login_state.dart';
-
-class LoginCubit extends Cubit<LoginState> {
- LoginCubit(
- this._authRepository, {
- required this.forced,
- }) : super(LoginState());
- final bool forced;
- final AuthenticationRepository _authRepository;
-
- Future<void> init() async {
- if (forced) {
- await _authRepository.logOut();
- }
- }
-
- Future<void> login() async {
- await _authRepository.openAuth(Prompt.login);
- }
-
- Future<void> createAccount() async {
- await _authRepository.openAuth(Prompt.create);
- }
-}
diff --git a/comwell_key_app/lib/login/cubit/login_state.dart b/comwell_key_app/lib/login/cubit/login_state.dart
deleted file mode 100644
index 7b3fec17..00000000
--- a/comwell_key_app/lib/login/cubit/login_state.dart
+++ /dev/null
@@ -1,7 +0,0 @@
-part of 'login_cubit.dart';
-
-class LoginState extends Equatable {
-
- @override
- List<Object> get props => [];
-}
\ No newline at end of file
diff --git a/comwell_key_app/lib/login/login_page.dart b/comwell_key_app/lib/login/login_page.dart
deleted file mode 100644
index b6aac67f..00000000
--- a/comwell_key_app/lib/login/login_page.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-import 'package:comwell_key_app/.generated/assets/assets.gen.dart';
-import 'package:comwell_key_app/login/components/forced_logout_banner.dart';
-import 'package:comwell_key_app/login/components/login_button.dart';
-import 'package:comwell_key_app/routing/app_routes.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
-
-import 'cubit/login_cubit.dart';
-
-class LoginPage extends StatelessWidget {
- const LoginPage({super.key});
-
- @override
- Widget build(BuildContext context) {
- return BlocBuilder<LoginCubit, LoginState>(
- builder: (context, state) {
- final cubit = context.read<LoginCubit>();
- return Scaffold(
- body: Container(
- constraints: const BoxConstraints.expand(),
- decoration: BoxDecoration(
- image: DecorationImage(
- image: AssetImage(Assets.images.loginScreenBackground.path),
- fit: BoxFit.cover,
- ),
- ),
- child: SafeArea(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- if (cubit.forced) const ForcedLogoutBanner(),
- // Logo
- Expanded(
- flex: 3,
- child: Assets.images.logo.image(width: 175, height: 50),
- ),
- const SizedBox(height: 32),
- LoginButton(
- onPressed: () async {
- await cubit.login();
- if(context.mounted) {
- context.go(AppRoutes.overview);
- }
- },
- ),
- const SizedBox(height: 20),
- ],
- ),
- ),
- ),
- );
- },
- );
- }
-}
diff --git a/comwell_key_app/lib/login/login_route.dart b/comwell_key_app/lib/login/login_route.dart
deleted file mode 100644
index a37b9654..00000000
--- a/comwell_key_app/lib/login/login_route.dart
+++ /dev/null
@@ -1,22 +0,0 @@
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:go_router/go_router.dart';
-
-import '../routing/app_routes.dart';
-import '../utils/locator.dart';
-import 'cubit/login_cubit.dart';
-import 'login_page.dart';
-
-RouteBase get loginRoute => GoRoute(
- path: AppRoutes.login,
- builder: (context, state) {
- final queryForced = state.uri.queryParameters['forced'] ?? false.toString();
- final forced = bool.tryParse(queryForced) ?? false;
- return BlocProvider(
- create: (context) => LoginCubit(
- locator.get(),
- forced: forced,
- ),
- child: const LoginPage(),
- );
- },
-);
diff --git a/comwell_key_app/lib/presentation/app/app_cubit.dart b/comwell_key_app/lib/presentation/app/app_cubit.dart
index 486f4c11..5b468e21 100644
--- a/comwell_key_app/lib/presentation/app/app_cubit.dart
+++ b/comwell_key_app/lib/presentation/app/app_cubit.dart
@@ -23,4 +23,8 @@ class AppCubit extends BaseCubit<AppState> {
}
});
}
+
+ void navigate(Uri uri) {
+ safeEmit(state.copyWith(event: Navigate(uri)));
+ }
}
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
new file mode 100644
index 00000000..61ffc507
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/login/bloc/login_cubit.dart
@@ -0,0 +1,44 @@
+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/base/base_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/login/bloc/login_state.dart';
+import 'package:comwell_key_app/routing/app_routes.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:go_router/go_router.dart';
+import 'package:msal_auth/msal_auth.dart';
+
+class LoginCubit extends BaseCubit<LoginState> {
+ LoginCubit(
+ this._authRepository,
+ this._appCubit, {
+ required this.forced,
+ required this.redirectAfterLogin,
+ }) : super(const LoginState());
+ final AuthenticationRepository _authRepository;
+ final AppCubit _appCubit;
+ final bool forced;
+ final String redirectAfterLogin;
+
+ Future<void> init() async {
+ if (forced) {
+ await _authRepository.logOut();
+ }
+ }
+
+ Future<void> login() async {
+ await _authRepository.openAuth(Prompt.login);
+ }
+
+ Future<void> createAccount() async {
+ await _authRepository.openAuth(Prompt.create);
+ }
+
+ 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);
+ }
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/login/bloc/login_state.dart b/comwell_key_app/lib/presentation/screens/login/bloc/login_state.dart
new file mode 100644
index 00000000..127bfb2c
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/login/bloc/login_state.dart
@@ -0,0 +1,12 @@
+import 'package:comwell_key_app/domain/models/app_error.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part '../../../../.generated/presentation/screens/login/bloc/login_state.freezed.dart';
+
+@freezed
+abstract class LoginState with _$LoginState {
+ const factory LoginState({
+ @Default(false) bool isLoading,
+ @Default(AppError.none) AppError error,
+ }) = _LoginState;
+}
diff --git a/comwell_key_app/lib/presentation/screens/login/components/create_user_button.dart b/comwell_key_app/lib/presentation/screens/login/components/create_user_button.dart
new file mode 100644
index 00000000..3349095a
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/login/components/create_user_button.dart
@@ -0,0 +1,33 @@
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+
+class CreateUserButton extends StatelessWidget {
+ final Function onPressed;
+
+ const CreateUserButton({
+ required this.onPressed,
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return OutlinedButton(
+ style: OutlinedButton.styleFrom(
+ backgroundColor: Colors.transparent,
+ side: const BorderSide(color: Colors.transparent),
+ ),
+ onPressed: () {
+ onPressed();
+ },
+ child: Text(
+ context.strings.create_user_button,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.surface,
+ fontSize: 16,
+ fontWeight: FontWeight.w600,
+ backgroundColor: Colors.transparent,
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/login/components/forced_logout_banner.dart b/comwell_key_app/lib/presentation/screens/login/components/forced_logout_banner.dart
new file mode 100644
index 00000000..2918f85e
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/login/components/forced_logout_banner.dart
@@ -0,0 +1,55 @@
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+
+class ForcedLogoutBanner extends StatelessWidget {
+ const ForcedLogoutBanner({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ return Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Container(
+ height: 100,
+ width: double.infinity,
+ decoration: BoxDecoration(
+ color: theme.colorScheme.surface,
+ borderRadius: BorderRadius.circular(10),
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
+ child: Row(
+ children: [
+ Container(
+ width: 64,
+ height: 64,
+ decoration: BoxDecoration(
+ color: Colors.red.withValues(alpha: 0.1),
+ shape: BoxShape.circle,
+ ),
+ child: const Icon(
+ Icons.error_outline,
+ color: Colors.red,
+ size: 36,
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(context.strings.error_logout,
+ style: theme.textTheme.titleMedium!.copyWith(
+ color: Colors.red, fontWeight: FontWeight.bold)),
+ Text(context.strings.error_logout_subtitle,
+ softWrap: true,
+ style: theme.textTheme.bodySmall!
+ .copyWith(color: Colors.red)),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/login/components/login_button.dart b/comwell_key_app/lib/presentation/screens/login/components/login_button.dart
new file mode 100644
index 00000000..82715cdf
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/login/components/login_button.dart
@@ -0,0 +1,34 @@
+import 'package:comwell_key_app/utils/l10n_utils.dart';
+import 'package:flutter/material.dart';
+
+class LoginButton extends StatelessWidget {
+ final VoidCallback onPressed;
+
+ const LoginButton({
+ required this.onPressed,
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 30),
+ child: ElevatedButton(
+ onPressed: onPressed,
+ style: ElevatedButton.styleFrom(
+ minimumSize: const Size(358, 52),
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ textStyle: TextStyle(
+ color: Theme.of(context).colorScheme.onSurface,
+ fontSize: 16,
+ fontWeight: FontWeight.w600,
+ ),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(30),
+ ),
+ ),
+ child: Text(context.strings.login_button),
+ ),
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/screens/login/login_route.dart b/comwell_key_app/lib/presentation/screens/login/login_route.dart
new file mode 100644
index 00000000..eaeb9222
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/login/login_route.dart
@@ -0,0 +1,35 @@
+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';
+
+import '../../../routing/app_routes.dart';
+
+part '../../../.generated/presentation/screens/login/login_route.g.dart';
+
+@TypedGoRoute<LoginRoute>(path: AppRoutes.login)
+class LoginRoute extends GoRouteData with $LoginRoute {
+ final bool forced;
+ final String redirectAfterLogin;
+
+ LoginRoute({this.forced = false, this.redirectAfterLogin = ""});
+
+ @override
+ Page<void> buildPage(BuildContext context, GoRouterState state) {
+ return SlideInTransition(
+ state: state,
+ child: BlocProvider(
+ create: (context) => LoginCubit(
+ locator.get(),
+ context.read(),
+ redirectAfterLogin: redirectAfterLogin,
+ forced: forced,
+ ),
+ child: const 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
new file mode 100644
index 00000000..39f258ad
--- /dev/null
+++ b/comwell_key_app/lib/presentation/screens/login/login_screen.dart
@@ -0,0 +1,55 @@
+import 'package:comwell_key_app/.generated/assets/assets.gen.dart';
+import 'package:comwell_key_app/presentation/screens/login/bloc/login_cubit.dart';
+import 'package:comwell_key_app/presentation/screens/login/components/forced_logout_banner.dart';
+import 'package:comwell_key_app/presentation/screens/login/components/login_button.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'bloc/login_state.dart';
+
+class LoginPage extends StatelessWidget {
+ const LoginPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder<LoginCubit, LoginState>(
+ builder: (context, state) {
+ final cubit = context.read<LoginCubit>();
+ return Scaffold(
+ body: Container(
+ constraints: const BoxConstraints.expand(),
+ decoration: BoxDecoration(
+ image: DecorationImage(
+ image: AssetImage(Assets.images.loginScreenBackground.path),
+ fit: BoxFit.cover,
+ ),
+ ),
+ child: SafeArea(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ if (cubit.forced) const ForcedLogoutBanner(),
+ // Logo
+ Expanded(
+ flex: 3,
+ child: Assets.images.logo.image(width: 175, height: 50),
+ ),
+ const SizedBox(height: 32),
+ LoginButton(
+ onPressed: () async {
+ await cubit.login();
+ if (context.mounted) {
+ cubit.redirect(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 29acdc7f..b8ff246a 100644
--- a/comwell_key_app/lib/routing/app_router.dart
+++ b/comwell_key_app/lib/routing/app_router.dart
@@ -13,7 +13,6 @@ import 'package:comwell_key_app/contact/contact_route.dart';
import 'package:comwell_key_app/find_booking/find_booking_route.dart';
import 'package:comwell_key_app/force_update/force_update_route.dart';
import 'package:comwell_key_app/key/key_route.dart';
-import 'package:comwell_key_app/login/login_route.dart';
import 'package:comwell_key_app/overview/overview_route.dart';
import 'package:comwell_key_app/presentation/screens/past_cancelled_booking_details/past_cancelled_booking_detail_route.dart';
import 'package:comwell_key_app/presentation/screens/webview/webview_route.dart';
@@ -29,13 +28,15 @@ import 'package:comwell_key_app/utils/context_utils.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
+import '../presentation/screens/login/login_route.dart';
import '../presentation/screens/onboarding/onboarding_routes.dart';
import '../utils/locator.dart';
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final rootNavigatorKey = _rootNavigatorKey;
-final authExceptions = [AppRoutes.webview];
+// These routes should not check if user is logged in
+final authExceptions = [AppRoutes.webview, AppRoutes.login];
final router = GoRouter(
initialLocation: AppRoutes.overview,
@@ -46,9 +47,11 @@ final router = GoRouter(
final authRepo = locator<AuthenticationRepository>();
final isLoggedIn = await 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'];
+
final Uri uri;
if (sharingType == 'RoomDistribution') {
uri = Uri(path: AppRoutes.receivedSharedRoom, queryParameters: state.uri.queryParameters);
@@ -66,7 +69,11 @@ final router = GoRouter(
}
if (!isLoggedIn) {
- return AppRoutes.login;
+ if (state.matchedLocation == AppRoutes.login) {
+ return LoginRoute().location;
+ } else {
+ return LoginRoute(redirectAfterLogin: state.matchedLocation).location;
+ }
}
if (context.mounted && !context.prefs.onboardingHasSeenUsageTracking) {
@@ -80,7 +87,7 @@ final router = GoRouter(
return null;
},
routes: <RouteBase>[
- loginRoute,
+ $loginRoute,
bluetoothPermissionRoute,
notificationsPermissionRoute,
usageTrackingPermissionRoute,