From cf9b0b0256c06b59a639a8f916464963ee81363d Mon Sep 17 00:00:00 2001 From: Mathias Mogensencd collaction_website Date: Sat, 4 Mar 2023 19:48:24 +0100 Subject: [PATCH 1/8] fix: add manual logging to auth repo --- .../auth/firebase_auth_repository.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/infrastructure/auth/firebase_auth_repository.dart b/lib/infrastructure/auth/firebase_auth_repository.dart index 0ec66fc7..42283e39 100644 --- a/lib/infrastructure/auth/firebase_auth_repository.dart +++ b/lib/infrastructure/auth/firebase_auth_repository.dart @@ -54,6 +54,13 @@ class FirebaseAuthRepository implements IAuthRepository, Disposable { result.add(right(AuthSuccess.codeSent(credential: credential))); }, verificationFailed: (firebase_auth.FirebaseAuthException error) { + FirebaseCrashlyticsLogger.warn( + error, + null, + message: + '[FirebaseAuthRepository] verifyPhoneNumber().verificationFailed', + ); + result.add(left(error.toFailure())); result.close(); }, @@ -166,6 +173,12 @@ class FirebaseAuthRepository implements IAuthRepository, Disposable { result.add(right(AuthSuccess.codeSent(credential: credential))); }, verificationFailed: (firebase_auth.FirebaseAuthException error) { + FirebaseCrashlyticsLogger.warn( + error, + null, + message: '[FirebaseAuthRepository] resendOTP().verificationFailed', + ); + result.add(left(error.toFailure())); result.close(); }, From 023d67eaef5b6040395cf03006731ed01cc28727 Mon Sep 17 00:00:00 2001 From: Mathias Mogensencd collaction_website Date: Sun, 5 Mar 2023 00:15:16 +0100 Subject: [PATCH 2/8] chore: upgrade gradle build tools and dependencies --- android/build.gradle | 6 +++--- android/gradle/wrapper/gradle-wrapper.properties | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 2a330ec6..6b74e344 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,10 +6,10 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0' + classpath 'com.android.tools.build:gradle:7.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.gms:google-services:4.3.10' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1' + classpath 'com.google.gms:google-services:4.3.15' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4' } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 939efa29..cb24abda 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip From 9b7bb4de9542f64e14ac9434eff3123a73090bee Mon Sep 17 00:00:00 2001 From: Isaac Obella Date: Tue, 17 Jan 2023 09:25:39 +0300 Subject: [PATCH 3/8] ft: Enable deep-links --- android/app/src/main/AndroidManifest.xml | 64 ++++++++++++++++-------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ff7bab9f..e2087b2f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,54 +1,74 @@ + - + + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> + android:name="io.flutter.embedding.android.SplashScreenDrawable" + android:resource="@drawable/launch_background" /> + + + + + + - - + + + + + + + + - + + android:name="com.yalantis.ucrop.UCropActivity" + android:screenOrientation="portrait" + android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> - + - - - + + + - + From c2e7d12ca19835d09ae746f28333cbbb8c7279f2 Mon Sep 17 00:00:00 2001 From: Isaac Obella Date: Fri, 20 Jan 2023 19:48:48 +0300 Subject: [PATCH 4/8] ft: Load crowdactions from deeplink --- .../crowdaction_details/crowdaction_details_screen.dart | 2 +- lib/presentation/routes/app_routes.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart b/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart index 0cc56950..5cd22d49 100644 --- a/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart +++ b/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart @@ -28,7 +28,7 @@ class CrowdActionDetailsPage extends StatefulWidget { const CrowdActionDetailsPage({ super.key, this.crowdAction, - this.crowdActionId, + @PathParam("id") this.crowdActionId, }) : assert(crowdAction != null || crowdActionId != null); @override diff --git a/lib/presentation/routes/app_routes.dart b/lib/presentation/routes/app_routes.dart index 4bfe0835..b14a27c2 100644 --- a/lib/presentation/routes/app_routes.dart +++ b/lib/presentation/routes/app_routes.dart @@ -33,7 +33,7 @@ import '../shared_widgets/web_view_page.dart'; page: EmptyRouterPage, children: [ AutoRoute(path: '', page: CrowdActionHomeScreen), - AutoRoute(path: 'details', page: CrowdActionDetailsPage), + AutoRoute(path: 'details/:id', page: CrowdActionDetailsPage), AutoRoute(path: 'participants', page: CrowdActionParticipantsPage), AutoRoute(path: 'view-all', page: CrowdActionBrowsePage), ], @@ -48,7 +48,7 @@ import '../shared_widgets/web_view_page.dart'; page: UserProfilePage, ), AutoRoute( - path: 'details', + path: 'details/:id', page: CrowdActionDetailsPage, ), ], From 51dcd7fde17f4821c6d974c9356f8419bdbc49a6 Mon Sep 17 00:00:00 2001 From: Isaac Obella Date: Sat, 28 Jan 2023 20:36:10 +0300 Subject: [PATCH 5/8] ft: Load iOs associated domains configuration --- .github/workflows/ci.yml | 6 ++++++ ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Deeplink.xcconfig.example | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Runner/Runner.entitlements | 4 ++++ 5 files changed, 13 insertions(+) create mode 100644 ios/Flutter/Deeplink.xcconfig.example diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ea2b09c..d39ac8ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -236,6 +236,12 @@ jobs: - name: Generate app icons run: flutter pub run flutter_launcher_icons:main + - name: Prepare deeplink configuration file + run: | + echo "$DEEPLINK_XCCONFIG_CONTENTS" > ios/Flutter/Deeplink.xcconfig + env: + DEEPLINK_XCCONFIG_CONTENTS: ${{ secrets.IOS_DEEPLINK_CONFIG }} + - name: Install the Apple certificate and provisioning profile env: BUILD_CERTIFICATE_BASE64: ${{ secrets.IOS_CERT_P12 }} diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index ec97fc6f..5fe80297 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +1,3 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" +#include "Deeplink.xcconfig" diff --git a/ios/Flutter/Deeplink.xcconfig.example b/ios/Flutter/Deeplink.xcconfig.example new file mode 100644 index 00000000..af77d44c --- /dev/null +++ b/ios/Flutter/Deeplink.xcconfig.example @@ -0,0 +1 @@ +APP_STORE_ASSOCIATED_DOMAIN= diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index ac5f5b28..a2d05ae6 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,5 +1,6 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" +#include "Deeplink.xcconfig" // Only build for iPhone (1) TARGETED_DEVICE_FAMILY = 1 diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 903def2a..40f422b0 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -4,5 +4,9 @@ aps-environment development + com.apple.developer.associated-domains + + applinks:${APP_STORE_ASSOCIATED_DOMAIN} + From 8735456931c6c23d31714327ab5bbf2fe4e0bf3c Mon Sep 17 00:00:00 2001 From: Isaac Obella Date: Sat, 28 Jan 2023 23:47:32 +0300 Subject: [PATCH 6/8] ft: Launch password dialog from details Signed-off-by: Isaac Obella --- .../crowdaction_details_screen.dart | 10 ++ .../home/widgets/password_modal.dart | 28 +++- .../crowdaction_details_screen_test.dart | 150 ++++++++++++++++++ 3 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 test/presentation/crowdaction/crowdaction_details/crowdaction_details_screen_test.dart diff --git a/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart b/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart index 5cd22d49..1b4ae8a2 100644 --- a/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart +++ b/lib/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart @@ -9,6 +9,7 @@ import '../../../application/auth/auth_bloc.dart'; import '../../../application/crowdaction/crowdaction_details/crowdaction_details_bloc.dart'; import '../../../application/participation/participation_bloc.dart'; import '../../../application/user/profile_tab/profile_tab_bloc.dart'; +import '../../home/widgets/password_modal.dart'; import '../../routes/app_routes.gr.dart'; import '../../shared_widgets/commitments/commitment_card_list.dart'; import '../../shared_widgets/expandable_text.dart'; @@ -117,6 +118,15 @@ class CrowdActionDetailsPageState extends State { state.maybeMap( loadSuccess: (state) { crowdAction = state.crowdAction; + showPasswordModal( + context, + state.crowdAction, + onPasswordValid: (isValidated) { + if (isValidated) { + Navigator.of(context).pop(); + } + }, + ); }, orElse: () {}, ); diff --git a/lib/presentation/home/widgets/password_modal.dart b/lib/presentation/home/widgets/password_modal.dart index 26648b71..3980c3f4 100644 --- a/lib/presentation/home/widgets/password_modal.dart +++ b/lib/presentation/home/widgets/password_modal.dart @@ -10,10 +10,12 @@ import '../../../presentation/themes/constants.dart'; class PasswordModal extends StatefulWidget { final CrowdAction crowdAction; + final Function(bool)? onPasswordValid; const PasswordModal({ super.key, required this.crowdAction, + this.onPasswordValid, }); @override @@ -147,6 +149,11 @@ class _PasswordModalState extends State { addCrowdActionAccess(); + if (widget.onPasswordValid != null) { + widget.onPasswordValid?.call(true); + return; + } + context.router.replace( CrowdActionDetailsRoute( crowdAction: widget.crowdAction, @@ -173,14 +180,22 @@ class _PasswordModalState extends State { Future showPasswordModal( BuildContext context, - CrowdAction crowdAction, -) async { + CrowdAction crowdAction, { + Function(bool)? onPasswordValid, +}) async { final settingsRepository = getIt(); final accessList = await settingsRepository.getCrowdActionAccessList(); if (accessList.contains(crowdAction.id)) { - context.router.push( - CrowdActionDetailsRoute(crowdAction: crowdAction), + if (onPasswordValid != null) { + onPasswordValid(false); + return; + } + + context.router.replace( + CrowdActionDetailsRoute( + crowdAction: crowdAction, + ), ); } else { showModalBottomSheet( @@ -188,7 +203,10 @@ Future showPasswordModal( isScrollControlled: true, backgroundColor: Colors.transparent, constraints: const BoxConstraints(maxHeight: 350), - builder: (context) => PasswordModal(crowdAction: crowdAction), + builder: (context) => PasswordModal( + crowdAction: crowdAction, + onPasswordValid: onPasswordValid, + ), ); } } diff --git a/test/presentation/crowdaction/crowdaction_details/crowdaction_details_screen_test.dart b/test/presentation/crowdaction/crowdaction_details/crowdaction_details_screen_test.dart new file mode 100644 index 00000000..bd187285 --- /dev/null +++ b/test/presentation/crowdaction/crowdaction_details/crowdaction_details_screen_test.dart @@ -0,0 +1,150 @@ +import 'dart:async'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:collaction_app/application/auth/auth_bloc.dart'; +import 'package:collaction_app/application/crowdaction/crowdaction_details/crowdaction_details_bloc.dart'; +import 'package:collaction_app/application/participation/participation_bloc.dart'; +import 'package:collaction_app/application/participation/top_participants/top_participants_bloc.dart'; +import 'package:collaction_app/application/user/profile_tab/profile_tab_bloc.dart'; +import 'package:collaction_app/domain/core/i_settings_repository.dart'; +import 'package:collaction_app/domain/crowdaction/crowdaction.dart'; +import 'package:collaction_app/domain/user/user.dart'; +import 'package:collaction_app/presentation/crowdaction/crowdaction_details/crowdaction_details_screen.dart'; +import 'package:collaction_app/presentation/home/widgets/password_modal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../application/auth/auth_bloc.mocks.dart'; +import '../../../application/crowdaction/crowdaction_details/crowdaction_details_bloc.mocks.dart'; +import '../../../application/participation/participation_bloc.mock.dart'; +import '../../../application/participation/top_participants/top_participants_bloc.mocks.dart'; +import '../../../application/user/profile_tab/profile_tab_bloc.mocks.dart'; +import '../../../test_utilities.dart'; + +void main() { + late final CrowdActionDetailsBloc crowdActionDetailsBloc; + late final ProfileTabBloc profileTabBloc; + late final ParticipationBloc participationBloc; + late final TopParticipantsBloc topParticipantsBloc; + late final AuthBloc authBloc; + late final ISettingsRepository settingsRepository; + + final crowdAction = tCrowdaction; + + setUpAll(() { + dotenv.testLoad(fileInput: tDotEnv); + + crowdActionDetailsBloc = MockCrowdActionDetailsBloc(); + GetIt.I.registerSingleton(crowdActionDetailsBloc); + when(() => crowdActionDetailsBloc.state).thenAnswer( + (_) => CrowdActionDetailsState.initial(), + ); + whenListen( + crowdActionDetailsBloc, + Stream.fromIterable( + [ + CrowdActionDetailsState.initial(), + CrowdActionDetailsState.loadSuccess(crowdAction), + ], + ), + ); + + profileTabBloc = MockProfileTabBloc(); + when(() => profileTabBloc.state).thenAnswer( + (_) => ProfileTabState(crowdActions: [crowdAction]), + ); + + participationBloc = MockParticipationBloc(); + GetIt.I.registerSingleton(participationBloc); + when(() => participationBloc.state).thenAnswer( + (_) => ParticipationState.notParticipating(), + ); + + topParticipantsBloc = MockTopParticipantsBloc(); + GetIt.I.registerSingleton(topParticipantsBloc); + when(() => topParticipantsBloc.state).thenAnswer( + (_) => TopParticipantsState.initial(), + ); + + authBloc = MockAuthBloc(); + GetIt.I.registerSingleton(authBloc); + when(() => authBloc.state).thenAnswer( + (_) => AuthState.authenticated(User.anonymous), + ); + + settingsRepository = MockSettingsRepository(); + GetIt.I.registerSingleton(settingsRepository); + }); + + tearDownAll(() { + GetIt.I.unregister(); + GetIt.I.unregister(); + GetIt.I.unregister(); + GetIt.I.unregister(); + GetIt.I.unregister(); + }); + + testWidgets( + 'should launch [PasswordModal] ' + 'when crowdaction is not in access list', (tester) async { + when(() => settingsRepository.getCrowdActionAccessList()).thenAnswer( + (_) async => [], + ); + when( + () => settingsRepository.addCrowdActionAccess( + crowdActionId: crowdAction.id, + ), + ).thenAnswer((_) async {}); + + await tester.pumpCrowdActionsDetailPage( + crowdAction: crowdAction, + profileTabBloc: profileTabBloc, + ); + + await tester.pump(); + + final findPasswordModal = find.byType(PasswordModal); + expect(findPasswordModal, findsOneWidget); + }); + + testWidgets( + 'should not launch [PasswordModal] ' + 'when crowdaction is in access list', (tester) async { + when(() => settingsRepository.getCrowdActionAccessList()).thenAnswer( + (_) async => [crowdAction.id], + ); + + await tester.pumpCrowdActionsDetailPage( + crowdAction: crowdAction, + profileTabBloc: profileTabBloc, + ); + + await tester.pump(); + + expect(find.byType(PasswordModal), findsNothing); + }); +} + +extension WidgetTesterX on WidgetTester { + Future pumpCrowdActionsDetailPage({ + required CrowdAction crowdAction, + required ProfileTabBloc profileTabBloc, + }) async { + await pumpWidget( + BlocProvider.value( + value: profileTabBloc, + child: MaterialApp( + home: Scaffold( + body: CrowdActionDetailsPage( + crowdAction: crowdAction, + ), + ), + ), + ), + ); + } +} From 0c8e724da4540a29479965ceb395453035f18915 Mon Sep 17 00:00:00 2001 From: Isaac Obella Date: Sat, 28 Jan 2023 23:56:05 +0300 Subject: [PATCH 7/8] fix: ignore deeplink config Signed-off-by: Isaac Obella --- ios/.gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/.gitignore b/ios/.gitignore index 151026b9..70ec2057 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -31,3 +31,6 @@ Runner/GeneratedPluginRegistrant.* !default.mode2v3 !default.pbxuser !default.perspectivev3 + +# Deeplink configuration file +Flutter/Deeplink.xcconfig From 25f89aef647bde9f8c71bbc84de9c51c6f13769e Mon Sep 17 00:00:00 2001 From: Isaac Obella Date: Tue, 7 Mar 2023 19:54:33 +0300 Subject: [PATCH 8/8] fix: Failing test Signed-off-by: Isaac Obella --- test/presentation/home/widgets/password_modal_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/presentation/home/widgets/password_modal_test.dart b/test/presentation/home/widgets/password_modal_test.dart index 7a7155c6..0a8571cf 100644 --- a/test/presentation/home/widgets/password_modal_test.dart +++ b/test/presentation/home/widgets/password_modal_test.dart @@ -166,7 +166,7 @@ void main() { await tester.pumpAndSettle(); final capturedRoutes = - verify(() => stackRouter.push(captureAny())).captured; + verify(() => stackRouter.replace(captureAny())).captured; expect(capturedRoutes.length, 1); expect(capturedRoutes.first, isA());