6177214e-ce7c-49e3-99de-ff9721b26f63 — Commit c61facca
Changed files
.../android/app/src/main/AndroidManifest.xml | 2 +- comwell_key_app/ios/Runner/Info.plist | 4 +- .../presentation/app/app_state.freezed.dart | 277 +++++++++++++++++++++ comwell_key_app/lib/comwell_app.dart | 4 + comwell_key_app/lib/overview/overview_route.dart | 7 +- .../lib/presentation/app/app_cubit.dart | 26 ++ .../lib/presentation/app/app_event_listener.dart | 62 +++++ .../lib/presentation/app/app_events.dart | 14 ++ .../lib/presentation/app/app_state.dart | 14 ++ comwell_key_app/pubspec.yaml | 1 + 10 files changed, 405 insertions(+), 6 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 e318f562..b5c50699 100644
--- a/comwell_key_app/android/app/src/main/AndroidManifest.xml
+++ b/comwell_key_app/android/app/src/main/AndroidManifest.xml
@@ -55,7 +55,7 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
- <meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
+ <meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
<intent-filter>
<data
android:host="${applicationId}"
diff --git a/comwell_key_app/ios/Runner/Info.plist b/comwell_key_app/ios/Runner/Info.plist
index 1194b9bc..6bb4ee28 100644
--- a/comwell_key_app/ios/Runner/Info.plist
+++ b/comwell_key_app/ios/Runner/Info.plist
@@ -56,7 +56,6 @@
<string>checkoutshopper-test.adyen.com</string>
<key>CFBundleURLSchemes</key>
<array>
- <string>app</string>
<string>https</string>
<string>adyencheckout</string>
</array>
@@ -68,7 +67,6 @@
<string>Comwell share booking</string>
<key>CFBundleURLSchemes</key>
<array>
- <string>app</string>
<string>comwell</string>
</array>
</dict>
@@ -79,7 +77,7 @@
<key>FIREBASE_ANALYTICS_COLLECTION_ENABLED</key>
<false/>
<key>FlutterDeepLinkingEnabled</key>
- <true/>
+ <false/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>msauthv2</string>
diff --git a/comwell_key_app/lib/.generated/presentation/app/app_state.freezed.dart b/comwell_key_app/lib/.generated/presentation/app/app_state.freezed.dart
new file mode 100644
index 00000000..3ad0e871
--- /dev/null
+++ b/comwell_key_app/lib/.generated/presentation/app/app_state.freezed.dart
@@ -0,0 +1,277 @@
+// 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/app/app_state.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+// dart format off
+T _$identity<T>(T value) => value;
+/// @nodoc
+mixin _$AppState {
+
+ bool get isLoading; AppError get error; AppEvent? get event;
+/// Create a copy of AppState
+/// with the given fields replaced by the non-null parameter values.
+@JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+$AppStateCopyWith<AppState> get copyWith => _$AppStateCopyWithImpl<AppState>(this as AppState, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.error, error) || other.error == error)&&(identical(other.event, event) || other.event == event));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,error,event);
+
+@override
+String toString() {
+ return 'AppState(isLoading: $isLoading, error: $error, event: $event)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class $AppStateCopyWith<$Res> {
+ factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl;
+@useResult
+$Res call({
+ bool isLoading, AppError error, AppEvent? event
+});
+
+
+
+
+}
+/// @nodoc
+class _$AppStateCopyWithImpl<$Res>
+ implements $AppStateCopyWith<$Res> {
+ _$AppStateCopyWithImpl(this._self, this._then);
+
+ final AppState _self;
+ final $Res Function(AppState) _then;
+
+/// Create a copy of AppState
+/// with the given fields replaced by the non-null parameter values.
+@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? error = null,Object? event = freezed,}) {
+ 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,event: freezed == event ? _self.event : event // ignore: cast_nullable_to_non_nullable
+as AppEvent?,
+ ));
+}
+
+}
+
+
+/// Adds pattern-matching-related methods to [AppState].
+extension AppStatePatterns on AppState {
+/// 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( _AppState value)? $default,{required TResult orElse(),}){
+final _that = this;
+switch (_that) {
+case _AppState() 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( _AppState value) $default,){
+final _that = this;
+switch (_that) {
+case _AppState():
+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( _AppState value)? $default,){
+final _that = this;
+switch (_that) {
+case _AppState() 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, AppEvent? event)? $default,{required TResult orElse(),}) {final _that = this;
+switch (_that) {
+case _AppState() when $default != null:
+return $default(_that.isLoading,_that.error,_that.event);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, AppEvent? event) $default,) {final _that = this;
+switch (_that) {
+case _AppState():
+return $default(_that.isLoading,_that.error,_that.event);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, AppEvent? event)? $default,) {final _that = this;
+switch (_that) {
+case _AppState() when $default != null:
+return $default(_that.isLoading,_that.error,_that.event);case _:
+ return null;
+
+}
+}
+
+}
+
+/// @nodoc
+
+
+class _AppState implements AppState {
+ const _AppState({this.isLoading = false, this.error = AppError.none, this.event = null});
+
+
+@override@JsonKey() final bool isLoading;
+@override@JsonKey() final AppError error;
+@override@JsonKey() final AppEvent? event;
+
+/// Create a copy of AppState
+/// with the given fields replaced by the non-null parameter values.
+@override @JsonKey(includeFromJson: false, includeToJson: false)
+@pragma('vm:prefer-inline')
+_$AppStateCopyWith<_AppState> get copyWith => __$AppStateCopyWithImpl<_AppState>(this, _$identity);
+
+
+
+@override
+bool operator ==(Object other) {
+ return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.error, error) || other.error == error)&&(identical(other.event, event) || other.event == event));
+}
+
+
+@override
+int get hashCode => Object.hash(runtimeType,isLoading,error,event);
+
+@override
+String toString() {
+ return 'AppState(isLoading: $isLoading, error: $error, event: $event)';
+}
+
+
+}
+
+/// @nodoc
+abstract mixin class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res> {
+ factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl;
+@override @useResult
+$Res call({
+ bool isLoading, AppError error, AppEvent? event
+});
+
+
+
+
+}
+/// @nodoc
+class __$AppStateCopyWithImpl<$Res>
+ implements _$AppStateCopyWith<$Res> {
+ __$AppStateCopyWithImpl(this._self, this._then);
+
+ final _AppState _self;
+ final $Res Function(_AppState) _then;
+
+/// Create a copy of AppState
+/// with the given fields replaced by the non-null parameter values.
+@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? error = null,Object? event = freezed,}) {
+ return _then(_AppState(
+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,event: freezed == event ? _self.event : event // ignore: cast_nullable_to_non_nullable
+as AppEvent?,
+ ));
+}
+
+
+}
+
+// dart format on
diff --git a/comwell_key_app/lib/comwell_app.dart b/comwell_key_app/lib/comwell_app.dart
index 90f254f6..a559db22 100644
--- a/comwell_key_app/lib/comwell_app.dart
+++ b/comwell_key_app/lib/comwell_app.dart
@@ -4,6 +4,7 @@ import 'package:comwell_key_app/connection_state/connection_state_cubit.dart';
import 'package:comwell_key_app/key/bloc/key_bloc.dart';
import 'package:comwell_key_app/key/repository/key_repository.dart';
import 'package:comwell_key_app/overview/cubit/overview_cubit.dart';
+import 'package:comwell_key_app/presentation/app/app_cubit.dart';
import 'package:comwell_key_app/profile/cubit/profile_cubit.dart';
import 'package:comwell_key_app/profile_settings/cubit/profile_settings_cubit.dart';
import 'package:comwell_key_app/routing/app_router.dart';
@@ -39,6 +40,9 @@ class ComwellApp extends StatelessWidget {
],
child: MultiBlocProvider(
providers: [
+ BlocProvider(
+ create: (context) => AppCubit(),
+ ),
BlocProvider(
create: (context) => ConnectionStateCubit(
locator.get(),
diff --git a/comwell_key_app/lib/overview/overview_route.dart b/comwell_key_app/lib/overview/overview_route.dart
index 9f0e7451..8ec3f393 100644
--- a/comwell_key_app/lib/overview/overview_route.dart
+++ b/comwell_key_app/lib/overview/overview_route.dart
@@ -1,4 +1,5 @@
import 'package:comwell_key_app/connection_state/connection_state_listener.dart';
+import 'package:comwell_key_app/presentation/app/app_event_listener.dart';
import 'package:go_router/go_router.dart';
import '../routing/app_routes.dart';
@@ -8,8 +9,10 @@ RouteBase overviewRoute = GoRoute(
path: AppRoutes.overview,
pageBuilder: (context, state) {
return const NoTransitionPage(
- child: ConnectionStateListener(
- child: OverviewPage(),
+ child: AppEventListener(
+ child: ConnectionStateListener(
+ child: OverviewPage(),
+ ),
),
);
},
diff --git a/comwell_key_app/lib/presentation/app/app_cubit.dart b/comwell_key_app/lib/presentation/app/app_cubit.dart
new file mode 100644
index 00000000..3e42690c
--- /dev/null
+++ b/comwell_key_app/lib/presentation/app/app_cubit.dart
@@ -0,0 +1,26 @@
+import 'dart:async';
+
+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';
+
+class AppCubit extends BaseCubit<AppState> {
+ AppCubit() : super(const AppState()) {
+ _init();
+ }
+
+ late final StreamSubscription<Uri> appLinksSubscription;
+
+ void _init() {
+ appLinksSubscription = AppLinks().uriLinkStream.listen((uri) {
+ debugPrint("qqq AppLinks uri=$uri");
+ final scheme = uri.scheme.toLowerCase();
+ final isDeeplink = scheme == "https" || scheme == "comwell";
+ if (isDeeplink) {
+ safeEmit(state.copyWith(event: Navigate(uri)));
+ }
+ });
+ }
+}
diff --git a/comwell_key_app/lib/presentation/app/app_event_listener.dart b/comwell_key_app/lib/presentation/app/app_event_listener.dart
new file mode 100644
index 00000000..97c003dd
--- /dev/null
+++ b/comwell_key_app/lib/presentation/app/app_event_listener.dart
@@ -0,0 +1,62 @@
+import 'dart:async';
+
+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:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:go_router/go_router.dart';
+
+import '../../routing/app_routes.dart';
+
+class AppEventListener extends StatelessWidget {
+ const AppEventListener({super.key, required this.child});
+
+ final Widget child;
+
+ void onEvent(BuildContext context, AppEvent event) {
+ switch (event) {
+ case Navigate _:
+ _onNavigateEvent(context, event.uri);
+ }
+ }
+
+ void _onNavigateEvent(BuildContext context, Uri uri) {
+ final route = "/${uri.host}${uri.path}";
+ final routeWithData = "$route?${uri.query}";
+ print("qqq route=${route}");
+ switch (route) {
+ case AppRoutes.splash:
+ // no-op, app will open at whatever screen it was showing
+ _goToRoute(context, AppRoutes.overview);
+ break;
+ case AppRoutes.overview:
+ _goToRoute(context, route);
+ break;
+ default:
+ _pushRoute(context, routeWithData);
+ }
+ }
+
+ Future<void> _pushRoute(BuildContext context, String route) async {
+ await Future<void>.delayed(const Duration(milliseconds: 500)); // UX delay
+ if (context.mounted) await context.push(route);
+ }
+
+ Future<void> _goToRoute(BuildContext context, String route) async {
+ await Future<void>.delayed(const Duration(milliseconds: 500)); // UX delay
+ if (context.mounted) context.go(route);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocListener<AppCubit, AppState>(
+ listenWhen: (prev, curr) => prev.event != curr.event,
+ listener: (context, state) {
+ final event = state.event;
+ if (event != null) onEvent(context, event);
+ },
+ child: child,
+ );
+ }
+}
diff --git a/comwell_key_app/lib/presentation/app/app_events.dart b/comwell_key_app/lib/presentation/app/app_events.dart
new file mode 100644
index 00000000..0f5aae74
--- /dev/null
+++ b/comwell_key_app/lib/presentation/app/app_events.dart
@@ -0,0 +1,14 @@
+sealed class AppEvent {
+ const AppEvent();
+}
+
+final class Navigate extends AppEvent {
+ final Uri uri;
+
+ const Navigate(this.uri);
+
+ @override
+ String toString() {
+ return "Navigate(path=$uri)";
+ }
+}
diff --git a/comwell_key_app/lib/presentation/app/app_state.dart b/comwell_key_app/lib/presentation/app/app_state.dart
new file mode 100644
index 00000000..15fa67fd
--- /dev/null
+++ b/comwell_key_app/lib/presentation/app/app_state.dart
@@ -0,0 +1,14 @@
+import 'package:comwell_key_app/domain/models/app_error.dart';
+import 'package:comwell_key_app/presentation/app/app_events.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part '../../.generated/presentation/app/app_state.freezed.dart';
+
+@freezed
+abstract class AppState with _$AppState {
+ const factory AppState({
+ @Default(false) bool isLoading,
+ @Default(AppError.none) AppError error,
+ @Default(null) AppEvent? event,
+ }) = _AppState;
+}
\ No newline at end of file
diff --git a/comwell_key_app/pubspec.yaml b/comwell_key_app/pubspec.yaml
index 6581a301..b18ce415 100644
--- a/comwell_key_app/pubspec.yaml
+++ b/comwell_key_app/pubspec.yaml
@@ -72,6 +72,7 @@ dependencies:
internet_connection_checker_plus: ^2.9.1+2
app_tracking_transparency: ^2.0.6+1
webview_flutter: ^4.13.1
+ app_links: ^6.4.1
dependency_overrides:
#Remove override when slider button updates