diff --git a/lib/core/enums/amplitude_events.dart b/lib/core/enums/amplitude_events.dart index 296347527..583cfc04b 100644 --- a/lib/core/enums/amplitude_events.dart +++ b/lib/core/enums/amplitude_events.dart @@ -333,6 +333,8 @@ enum AmplitudeEvents { familyReflectSummaryQuestionsAskedClicked( 'family_reflect_summary_questions_asked_clicked', ), + parentReflectionFlowOrganisationClicked( + 'parent_reflection_flow_organisation_clicked'), familyReflectSummaryGenerousDeedsClicked( 'family_reflect_summary_generous_deeds_clicked', ), diff --git a/lib/features/children/generosity_challenge/assignments/create_challenge_donation/pages/choose_amount_slider_page.dart b/lib/features/children/generosity_challenge/assignments/create_challenge_donation/pages/choose_amount_slider_page.dart index 060853db3..701ff3c0b 100644 --- a/lib/features/children/generosity_challenge/assignments/create_challenge_donation/pages/choose_amount_slider_page.dart +++ b/lib/features/children/generosity_challenge/assignments/create_challenge_donation/pages/choose_amount_slider_page.dart @@ -40,7 +40,7 @@ class ChooseAmountSliderPage extends StatefulWidget { class _ChooseAmountSliderPageState extends State { bool _isLoading = false; - + final give = getIt(); String _createAssignmentDescription(String organisationName, double amount) { final intAmount = amount.toInt(); return 'You gave $intAmount dollar${intAmount > 1 ? 's' : ''} to the $organisationName. Awesome!'; @@ -52,6 +52,7 @@ class _ChooseAmountSliderPageState extends State { CreateChallengeDonationState>( builder: (context, state) { return BlocListener( + bloc: give, listener: (context, giveState) async { if (giveState.status == GiveStatus.processed) { _setLoading(false); @@ -186,7 +187,7 @@ class _ChooseAmountSliderPageState extends State { final decodedMediumId = utf8.decode(base64.decode(widget.organisation.mediumId!)); - context.read() + give ..add( GiveAmountChanged( firstCollectionAmount: state.amount, @@ -211,7 +212,7 @@ class _ChooseAmountSliderPageState extends State { if (e is StripeException && e.error.code == FailureCode.Canceled) { // do nothing } else { - context.read().add(const GiveStripeRegistrationError()); + give.add(const GiveStripeRegistrationError()); LoggingInfo.instance.info( e.toString(), methodName: stackTrace.toString(), diff --git a/lib/features/family/app/family_routes.dart b/lib/features/family/app/family_routes.dart index ed6f51f22..4627a5058 100644 --- a/lib/features/family/app/family_routes.dart +++ b/lib/features/family/app/family_routes.dart @@ -56,7 +56,6 @@ import 'package:givt_app/features/family/features/impact_groups/cubit/impact_gro import 'package:givt_app/features/family/features/impact_groups/model/goal.dart'; import 'package:givt_app/features/family/features/impact_groups/model/impact_group.dart'; import 'package:givt_app/features/family/features/impact_groups/pages/impact_group_details_page.dart'; -import 'package:givt_app/features/family/features/parent_giving_flow/cubit/medium_cubit.dart'; import 'package:givt_app/features/family/features/parent_giving_flow/presentation/pages/give_from_list_page.dart'; import 'package:givt_app/features/family/features/parent_giving_flow/presentation/pages/parent_giving_page.dart'; import 'package:givt_app/features/family/features/profiles/cubit/profiles_cubit.dart'; @@ -73,7 +72,6 @@ import 'package:givt_app/features/family/features/recommendation/tags/cubit/tags import 'package:givt_app/features/family/features/recommendation/tags/screens/location_selection_screen.dart'; import 'package:givt_app/features/family/features/reflect/presentation/pages/reflect_intro_screen.dart'; import 'package:givt_app/features/family/features/scan_nfc/nfc_scan_screen.dart'; -import 'package:givt_app/features/give/bloc/give/give_bloc.dart'; import 'package:givt_app/features/give/models/organisation.dart'; import 'package:givt_app/features/permit_biometric/cubit/permit_biometric_cubit.dart'; import 'package:givt_app/features/permit_biometric/models/permit_biometric_request.dart'; @@ -222,14 +220,6 @@ class FamilyAppRoutes { return MultiBlocProvider( providers: [ - BlocProvider( - create: (_) => GiveBloc( - getIt(), - getIt(), - getIt(), - getIt(), - ), - ), BlocProvider( create: (context) => CreateChallengeDonationCubit(), ), @@ -286,17 +276,6 @@ class FamilyAppRoutes { final user = context.read().state.user; return MultiBlocProvider( providers: [ - BlocProvider( - create: (_) => GiveBloc( - getIt(), - getIt(), - getIt(), - getIt(), - ), - ), - BlocProvider( - create: (_) => MediumCubit(), - ), BlocProvider( create: (_) => OrganisationBloc( getIt(), @@ -316,10 +295,7 @@ class FamilyAppRoutes { GoRoute( path: FamilyPages.parentGive.path, name: FamilyPages.parentGive.name, - builder: (context, state) => BlocProvider.value( - value: state.extra! as GiveBloc, - child: const ParentGivingPage(), - ), + builder: (context, state) => const ParentGivingPage(), ), GoRoute( path: FamilyPages.wallet.path, diff --git a/lib/features/family/app/injection.dart b/lib/features/family/app/injection.dart index b8c028677..5d4f34e0e 100644 --- a/lib/features/family/app/injection.dart +++ b/lib/features/family/app/injection.dart @@ -8,6 +8,7 @@ import 'package:givt_app/features/family/features/giving_flow/collectgroup_detai import 'package:givt_app/features/family/features/giving_flow/create_transaction/repositories/create_transaction_repository.dart'; import 'package:givt_app/features/family/features/history/history_repository/history_repository.dart'; import 'package:givt_app/features/family/features/impact_groups/repository/impact_groups_repository.dart'; +import 'package:givt_app/features/family/features/parent_giving_flow/cubit/medium_cubit.dart'; import 'package:givt_app/features/family/features/profiles/repository/profiles_repository.dart'; import 'package:givt_app/features/family/features/qr_scanner/cubit/camera_cubit.dart'; import 'package:givt_app/features/family/features/recommendation/organisations/repositories/organisations_repository.dart'; @@ -22,6 +23,7 @@ import 'package:givt_app/features/family/features/reflect/domain/grateful_recomm import 'package:givt_app/features/family/features/reflect/domain/reflect_and_share_repository.dart'; import 'package:givt_app/features/family/helpers/svg_manager.dart'; import 'package:givt_app/features/family/network/api_service.dart'; +import 'package:givt_app/features/give/bloc/bloc.dart'; final getIt = GetIt.instance; @@ -53,6 +55,15 @@ void initCubits() { ..registerLazySingleton( CameraCubit.new, ) + ..registerLazySingleton(MediumCubit.new) + ..registerLazySingleton( + () => GiveBloc( + getIt(), + getIt(), + getIt(), + getIt(), + ), + ) ..registerFactory( () => FamilyRolesCubit( getIt(), diff --git a/lib/features/family/features/parent_giving_flow/presentation/pages/give_from_list_page.dart b/lib/features/family/features/parent_giving_flow/presentation/pages/give_from_list_page.dart index b1e386b5f..012d6a0e5 100644 --- a/lib/features/family/features/parent_giving_flow/presentation/pages/give_from_list_page.dart +++ b/lib/features/family/features/parent_giving_flow/presentation/pages/give_from_list_page.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:givt_app/app/injection/injection.dart'; import 'package:givt_app/core/enums/amplitude_events.dart'; import 'package:givt_app/core/enums/collect_group_type.dart'; import 'package:givt_app/features/auth/cubit/auth_cubit.dart'; @@ -23,22 +24,22 @@ class GiveFromListPage extends StatelessWidget { @override Widget build(BuildContext context) { final locals = context.l10n; - + final give = getIt(); return BlocConsumer( + bloc: give, listener: (context, state) { final userGUID = context.read().state.user.guid; if (state.status == GiveStatus.success) { - context.read().add( - GiveOrganisationSelected( - nameSpace: context.read().state.mediumId, - userGUID: userGUID, - ), - ); + give.add( + GiveOrganisationSelected( + nameSpace: getIt().state.mediumId, + userGUID: userGUID, + ), + ); } if (state.status == GiveStatus.readyToGive) { context.pushReplacementNamed( FamilyPages.parentGive.name, - extra: context.read(), ); } if (state.status == GiveStatus.error) { @@ -73,7 +74,7 @@ class GiveFromListPage extends StatelessWidget { }, ), ); - context.read().setMediumId(collectGroup.nameSpace); + getIt().setMediumId(collectGroup.nameSpace); final dynamic result = await Navigator.push( context, ParentAmountPage( @@ -94,13 +95,13 @@ class GiveFromListPage extends StatelessWidget { }, ), ); - context.read().add( - GiveAmountChanged( - firstCollectionAmount: result.toDouble(), - secondCollectionAmount: 0, - thirdCollectionAmount: 0, - ), - ); + getIt().add( + GiveAmountChanged( + firstCollectionAmount: result.toDouble(), + secondCollectionAmount: 0, + thirdCollectionAmount: 0, + ), + ); } } } diff --git a/lib/features/family/features/parent_giving_flow/presentation/pages/parent_amount_page.dart b/lib/features/family/features/parent_giving_flow/presentation/pages/parent_amount_page.dart index cb7fa7c40..b4ac26b50 100644 --- a/lib/features/family/features/parent_giving_flow/presentation/pages/parent_amount_page.dart +++ b/lib/features/family/features/parent_giving_flow/presentation/pages/parent_amount_page.dart @@ -78,13 +78,17 @@ class _ParentAmountPageState extends State { ), ), ), - floatingActionButton: FunButton( - onTap: () { - Navigator.of(context).pop(_amount); - }, - text: 'Give', - analyticsEvent: AnalyticsEvent( - AmplitudeEvents.parentGiveClicked, + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: FunButton( + onTap: () { + Navigator.of(context).pop(_amount); + }, + text: 'Give', + analyticsEvent: AnalyticsEvent( + AmplitudeEvents.parentGiveClicked, + ), ), ), ); diff --git a/lib/features/family/features/parent_giving_flow/presentation/pages/parent_giving_page.dart b/lib/features/family/features/parent_giving_flow/presentation/pages/parent_giving_page.dart index 39afed7d3..cec137602 100644 --- a/lib/features/family/features/parent_giving_flow/presentation/pages/parent_giving_page.dart +++ b/lib/features/family/features/parent_giving_flow/presentation/pages/parent_giving_page.dart @@ -28,6 +28,7 @@ class ParentGivingPage extends StatefulWidget { class _ParentGivingPageState extends State { late CustomInAppBrowser _customInAppBrowser; bool browserIsOpened = false; + final give = getIt(); @override void initState() { @@ -63,8 +64,7 @@ class _ParentGivingPageState extends State { context.read().fetchImpactGroups(), ); - final afterGivingRedirection = - context.read().state.afterGivingRedirection; + final afterGivingRedirection = give.state.afterGivingRedirection; context.pop(); if (afterGivingRedirection.isNotEmpty) { @@ -82,14 +82,13 @@ class _ParentGivingPageState extends State { Map _buildGivt( BuildContext context, ) { - final giveBlocState = context.read().state; final user = context.read().state.user; final format = NumberFormat.simpleCurrency( - name: giveBlocState.organisation.currency, + name: give.state.organisation.currency, ); - var orgName = giveBlocState.organisation.organisationName!; - final instanceName = giveBlocState.instanceName; - if (giveBlocState.instanceName.isNotEmpty && instanceName != orgName) { + var orgName = give.state.organisation.organisationName!; + final instanceName = give.state.instanceName; + if (give.state.instanceName.isNotEmpty && instanceName != orgName) { orgName = '$orgName: $instanceName'; } return WebViewInput( @@ -97,13 +96,13 @@ class _ParentGivingPageState extends State { apiUrl: Uri.https(getIt().apiURL).toString(), guid: user.guid, organisation: orgName, - givtObj: GivtTransaction.toJsonList(giveBlocState.givtTransactions), + givtObj: GivtTransaction.toJsonList(give.state.givtTransactions), confirmBtn: context.l10n.next, cancel: context.l10n.cancel, areYouSureToCancelGivts: context.l10n.areYouSureToCancelGivts, message: context.l10n.safariGivtTransaction, thanks: context.l10n.givtIsBeingProcessed( - giveBlocState.organisation.organisationName.toString(), + give.state.organisation.organisationName.toString(), ), yesSuccess: context.l10n.yesSuccess, close: context.l10n.close, diff --git a/lib/features/family/features/reflect/bloc/grateful_cubit.dart b/lib/features/family/features/reflect/bloc/grateful_cubit.dart index 707199632..8746f8802 100644 --- a/lib/features/family/features/reflect/bloc/grateful_cubit.dart +++ b/lib/features/family/features/reflect/bloc/grateful_cubit.dart @@ -43,8 +43,8 @@ class GratefulCubit extends CommonCubit { _profiles ..sort((a, b) => a.isChild ? -1 : 1) ..removeWhere( - (element) => !element.isChild || element.gratitude == null, - ); //in the future we don't need to remove the adults + (element) => element.gratitude == null, + ); if (_profiles.isEmpty) { _onEveryoneDonated(); @@ -123,6 +123,14 @@ class GratefulCubit extends CommonCubit { } } + Future onParentDonated(String userId) async { + final parent = _profiles.firstWhere( + (e) => e.userId == userId, + orElse: () => throw Exception('Parent profile not found for userId: $userId'), + ); + await onDonated(parent); + } + Future onDonated(GameProfile profile) async { _reflectAndShareRepository.incrementGenerousDeeds(); _profilesThatDonated.add(profile); diff --git a/lib/features/family/features/reflect/presentation/pages/grateful_screen.dart b/lib/features/family/features/reflect/presentation/pages/grateful_screen.dart index e8f3e0de4..95df22a00 100644 --- a/lib/features/family/features/reflect/presentation/pages/grateful_screen.dart +++ b/lib/features/family/features/reflect/presentation/pages/grateful_screen.dart @@ -1,11 +1,21 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:givt_app/core/enums/amplitude_events.dart'; +import 'package:givt_app/core/enums/collect_group_type.dart'; +import 'package:givt_app/features/auth/cubit/auth_cubit.dart'; import 'package:givt_app/features/family/app/injection.dart'; import 'package:givt_app/features/family/extensions/extensions.dart'; import 'package:givt_app/features/family/features/giving_flow/create_transaction/cubit/create_transaction_cubit.dart'; import 'package:givt_app/features/family/features/giving_flow/screens/choose_amount_slider_screen.dart'; +import 'package:givt_app/features/family/features/parent_giving_flow/cubit/medium_cubit.dart'; +import 'package:givt_app/features/family/features/parent_giving_flow/presentation/pages/parent_amount_page.dart'; +import 'package:givt_app/features/family/features/parent_giving_flow/presentation/pages/parent_giving_page.dart'; import 'package:givt_app/features/family/features/profiles/cubit/profiles_cubit.dart'; +import 'package:givt_app/features/family/features/recommendation/organisations/models/organisation.dart'; import 'package:givt_app/features/family/features/reflect/bloc/grateful_cubit.dart'; +import 'package:givt_app/features/family/features/reflect/domain/models/game_profile.dart'; import 'package:givt_app/features/family/features/reflect/presentation/models/grateful_custom.dart'; import 'package:givt_app/features/family/features/reflect/presentation/pages/summary_screen.dart'; import 'package:givt_app/features/family/features/reflect/presentation/widgets/finish_reflection_dialog.dart'; @@ -14,8 +24,12 @@ import 'package:givt_app/features/family/features/reflect/presentation/widgets/g import 'package:givt_app/features/family/features/reflect/presentation/widgets/leave_game_button.dart'; import 'package:givt_app/features/family/features/reflect/presentation/widgets/recommendations_widget.dart'; import 'package:givt_app/features/family/shared/design/components/components.dart'; +import 'package:givt_app/features/family/shared/widgets/loading/full_screen_loading_widget.dart'; +import 'package:givt_app/features/give/bloc/give/give_bloc.dart'; +import 'package:givt_app/l10n/l10n.dart'; import 'package:givt_app/shared/widgets/base/base_state_consumer.dart'; import 'package:givt_app/shared/widgets/fun_scaffold.dart'; +import 'package:givt_app/utils/analytics_helper.dart'; import 'package:go_router/go_router.dart'; class GratefulScreen extends StatefulWidget { @@ -27,6 +41,8 @@ class GratefulScreen extends StatefulWidget { class _GratefulScreenState extends State { final _cubit = getIt(); + final _give = getIt(); + final _medium = getIt(); @override void didChangeDependencies() { @@ -42,67 +58,164 @@ class _GratefulScreenState extends State { @override Widget build(BuildContext context) { - return BaseStateConsumer( - cubit: _cubit, - onCustom: _handleCustom, - onLoading: (context) => const GratefulLoading(), - onData: (context, uiModel) { - return FunScaffold( - withSafeArea: false, - appBar: FunTopAppBar( - title: "You're grateful for", - actions: [ - LeaveGameButton( - onPressed: () => FinishReflectionDialog().show(context), - ), - ], - ), - body: Column( - children: [ - GratefulAvatarBar( - uiModel: uiModel.avatarBarUIModel, - onAvatarTapped: _cubit.onAvatarTapped), - Flexible( - child: RecommendationsWidget( - uiModel: uiModel.recommendationsUIModel, - onRecommendationChosen: (int i) { - _cubit.onRecommendationChosen(i); - context.pop(); - }, - onTapRetry: _cubit.onRetry, - ), - ), - ], - ), - ); + return BlocListener( + bloc: _give, + listener: (context, state) { + final userGUID = context.read().state.user.guid; + if (state.status == GiveStatus.success) { + _give.add( + GiveOrganisationSelected( + nameSpace: _medium.state.mediumId, + userGUID: userGUID, + ), + ); + } + if (state.status == GiveStatus.readyToGive) { + // we assume the parent confirms on browser + _handleParentBrowser(userGUID); + } + if (state.status == GiveStatus.error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(context.l10n.somethingWentWrong), + ), + ); + } }, + child: BaseStateConsumer( + cubit: _cubit, + onCustom: _handleCustom, + onLoading: (context) => const GratefulLoading(), + onData: (context, uiModel) { + return FunScaffold( + withSafeArea: false, + appBar: FunTopAppBar( + title: "You're grateful for", + actions: [ + LeaveGameButton( + onPressed: () => const FinishReflectionDialog().show(context), + ), + ], + ), + body: Column( + children: [ + GratefulAvatarBar( + uiModel: uiModel.avatarBarUIModel, + onAvatarTapped: _cubit.onAvatarTapped), + Flexible( + child: RecommendationsWidget( + uiModel: uiModel.recommendationsUIModel, + onRecommendationChosen: (int i) { + _cubit.onRecommendationChosen(i); + context.pop(); + }, + onTapRetry: _cubit.onRetry, + ), + ), + ], + ), + ); + }, + ), ); } void _handleCustom(BuildContext context, GratefulCustom custom) { switch (custom) { case final GratefulOpenKidDonationFlow data: - context.read().setActiveProfile(data.profile.userId); - Navigator.of(context).push( - BlocProvider( - create: (BuildContext context) => - CreateTransactionCubit(context.read(), getIt()), - child: ChooseAmountSliderScreen( - onCustomSuccess: () { - _cubit.onDonated(data.profile); - context.pop(); - }, - ), - ).toRoute(context), + _navigateToChildGivingScreen( + context, + data.profile, ); case final GratefulOpenParentDonationFlow data: - // TODO: Handle this case. - data.profile; + _navigateToParentGivingScreen( + context, + data.organisation, + ); case GratefulGoToGameSummary(): _navigateToSummary(context); } } + Future _navigateToChildGivingScreen( + BuildContext context, + GameProfile profile, + ) async { + await context.read().setActiveProfile(profile.userId); + await Navigator.of(context).push( + BlocProvider( + create: (BuildContext context) => + CreateTransactionCubit(context.read(), getIt()), + child: ChooseAmountSliderScreen( + onCustomSuccess: () { + _cubit.onDonated(profile); + context.pop(); + }, + ), + ).toRoute(context), + ); + } + + Future _navigateToParentGivingScreen( + BuildContext context, + Organisation org, + ) async { + _medium.setMediumId(org.namespace); + unawaited( + AnalyticsHelper.logEvent( + eventName: AmplitudeEvents.parentReflectionFlowOrganisationClicked, + eventProperties: { + 'organisation': org.name, + }, + ), + ); + final dynamic result = await Navigator.push( + context, + ParentAmountPage( + currency: r'$', + organisationName: org.name, + colorCombo: + CollectGroupType.getColorComboByType(CollectGroupType.charities), + icon: CollectGroupType.getIconByTypeUS(CollectGroupType.charities), + ).toRoute(context), + ); + if (result != null && result is int && context.mounted) { + unawaited( + AnalyticsHelper.logEvent( + eventName: AmplitudeEvents.parentGiveWithAmountClicked, + eventProperties: { + 'amount': result, + 'organisation': org.name, + 'mediumid': org.namespace, + }, + ), + ); + _give.add( + GiveAmountChanged( + firstCollectionAmount: result.toDouble(), + secondCollectionAmount: 0, + thirdCollectionAmount: 0, + ), + ); + await Navigator.push( + context, + const FullScreenLoadingWidget( + text: 'Setting up your donation', + ).toRoute(context), + ); + } + } + + Future _handleParentBrowser(String guid) async { + final dynamic result = await Navigator.pushReplacement( + context, + const ParentGivingPage().toRoute(context), + ); + if (result != null && result == true && context.mounted) { + await _cubit.onParentDonated(guid); + } + } + void _navigateToSummary(BuildContext context) { Navigator.of(context).push(const SummaryScreen().toRoute(context)); }