From c4d96a350c6af880725f5911166a367f2a46afa6 Mon Sep 17 00:00:00 2001 From: Maikel Stuivenberg Date: Fri, 13 Sep 2024 13:05:26 +0200 Subject: [PATCH] Logic --- lib/core/enums/amplitude_events.dart | 1 + .../screens/profile_selection_screen.dart | 2 +- .../reflect/bloc/interview_cubit.dart | 5 + .../features/reflect/bloc/summary_cubit.dart | 25 +++ .../domain/models/summary_details.dart | 6 + .../domain/reflect_and_share_repository.dart | 2 + .../pages/record_answer_screen.dart | 1 + .../pages/reflect_intro_screen.dart | 2 +- .../presentation/pages/result_screen.dart | 8 +- .../presentation/pages/summary_screen.dart | 92 +++++++++++ .../design/components/actions/fun_tile.dart | 149 +++++++++++------- .../widgets/layout/action_container.dart | 13 +- 12 files changed, 239 insertions(+), 67 deletions(-) create mode 100644 lib/features/family/features/reflect/bloc/summary_cubit.dart create mode 100644 lib/features/family/features/reflect/domain/models/summary_details.dart create mode 100644 lib/features/family/features/reflect/presentation/pages/summary_screen.dart diff --git a/lib/core/enums/amplitude_events.dart b/lib/core/enums/amplitude_events.dart index a6bdeff0e..3949c6468 100644 --- a/lib/core/enums/amplitude_events.dart +++ b/lib/core/enums/amplitude_events.dart @@ -317,6 +317,7 @@ enum AmplitudeEvents { reflectAndShareResultNextRoundClicked( 'reflect_and_share_result_next_round_clicked', ), + familyReflectSummaryBackToHome('family_reflect_summary_back_to_home'), // DEBUG ONLY debugButtonClicked('debug_button_clicked'), diff --git a/lib/features/family/features/profiles/screens/profile_selection_screen.dart b/lib/features/family/features/profiles/screens/profile_selection_screen.dart index ecf6d5463..2ac8b000c 100644 --- a/lib/features/family/features/profiles/screens/profile_selection_screen.dart +++ b/lib/features/family/features/profiles/screens/profile_selection_screen.dart @@ -168,7 +168,7 @@ class _ProfileSelectionScreenState extends State { onTap: () => context.goNamed( FamilyPages.reflectIntro.name, ), - text: 'Reflect & Share', + text: 'Graditude game', analyticsEvent: AnalyticsEvent( AmplitudeEvents.reflectAndShareClicked, ), diff --git a/lib/features/family/features/reflect/bloc/interview_cubit.dart b/lib/features/family/features/reflect/bloc/interview_cubit.dart index 7a8445da7..9a42eeb89 100644 --- a/lib/features/family/features/reflect/bloc/interview_cubit.dart +++ b/lib/features/family/features/reflect/bloc/interview_cubit.dart @@ -62,6 +62,7 @@ class InterviewCubit extends CommonCubit { // Advance to the next reporter/question void advanceToNext() { + _reflectAndShareRepository.totalQuestionsAsked++; _nrOfQuestionsAsked++; if (_isLastQuestion()) { interviewFinished(); @@ -100,4 +101,8 @@ class InterviewCubit extends CommonCubit { ), ); } + + void increaseTimeSpent() { + _reflectAndShareRepository.totalTimeSpent++; + } } diff --git a/lib/features/family/features/reflect/bloc/summary_cubit.dart b/lib/features/family/features/reflect/bloc/summary_cubit.dart new file mode 100644 index 000000000..06466675b --- /dev/null +++ b/lib/features/family/features/reflect/bloc/summary_cubit.dart @@ -0,0 +1,25 @@ +import 'package:givt_app/features/family/features/reflect/domain/models/summary_details.dart'; +import 'package:givt_app/features/family/features/reflect/domain/reflect_and_share_repository.dart'; +import 'package:givt_app/shared/bloc/base_state.dart'; +import 'package:givt_app/shared/bloc/common_cubit.dart'; + +class SummaryCubit extends CommonCubit { + SummaryCubit(this._reflectAndShareRepository) + : super(const BaseState.loading()); + + final ReflectAndShareRepository _reflectAndShareRepository; + + void init() { + final totalMinutesPlayed = + (_reflectAndShareRepository.totalTimeSpent / 60).round(); + final amountOfQuestionsAsked = + _reflectAndShareRepository.totalQuestionsAsked; + + emitData( + SummaryDetails( + minutesPlayed: totalMinutesPlayed, + questionsAsked: amountOfQuestionsAsked, + ), + ); + } +} diff --git a/lib/features/family/features/reflect/domain/models/summary_details.dart b/lib/features/family/features/reflect/domain/models/summary_details.dart new file mode 100644 index 000000000..9156b7150 --- /dev/null +++ b/lib/features/family/features/reflect/domain/models/summary_details.dart @@ -0,0 +1,6 @@ +class SummaryDetails { + SummaryDetails({required this.minutesPlayed, required this.questionsAsked}); + + int minutesPlayed; + int questionsAsked; +} diff --git a/lib/features/family/features/reflect/domain/reflect_and_share_repository.dart b/lib/features/family/features/reflect/domain/reflect_and_share_repository.dart index f65505a90..4bacf75f2 100644 --- a/lib/features/family/features/reflect/domain/reflect_and_share_repository.dart +++ b/lib/features/family/features/reflect/domain/reflect_and_share_repository.dart @@ -10,6 +10,8 @@ class ReflectAndShareRepository { final ProfilesRepository _profilesRepository; int completedLoops = 0; + int totalQuestionsAsked = 0; + int totalTimeSpent = 0; List? _allProfiles; List _selectedProfiles = []; diff --git a/lib/features/family/features/reflect/presentation/pages/record_answer_screen.dart b/lib/features/family/features/reflect/presentation/pages/record_answer_screen.dart index ecb6641ab..ca0609f5f 100644 --- a/lib/features/family/features/reflect/presentation/pages/record_answer_screen.dart +++ b/lib/features/family/features/reflect/presentation/pages/record_answer_screen.dart @@ -68,6 +68,7 @@ class _RecordAnswerScreenState extends State { if (!mounted) return; setState(() { + cubit.increaseTimeSpent(); _remainingSeconds--; }); } diff --git a/lib/features/family/features/reflect/presentation/pages/reflect_intro_screen.dart b/lib/features/family/features/reflect/presentation/pages/reflect_intro_screen.dart index d4314a0d9..fd88582c3 100644 --- a/lib/features/family/features/reflect/presentation/pages/reflect_intro_screen.dart +++ b/lib/features/family/features/reflect/presentation/pages/reflect_intro_screen.dart @@ -38,7 +38,7 @@ class _ReflectIntroScreenState extends State { create: (_) => _cubit, child: FunScaffold( appBar: const FunTopAppBar( - title: 'Reflect and share', + title: 'Graditude game', leading: GivtBackButtonFlat(), ), body: BlocConsumer( diff --git a/lib/features/family/features/reflect/presentation/pages/result_screen.dart b/lib/features/family/features/reflect/presentation/pages/result_screen.dart index 3be2a6720..ae3e8a61d 100644 --- a/lib/features/family/features/reflect/presentation/pages/result_screen.dart +++ b/lib/features/family/features/reflect/presentation/pages/result_screen.dart @@ -6,6 +6,7 @@ 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/reflect/bloc/result_cubit.dart'; import 'package:givt_app/features/family/features/reflect/presentation/pages/family_roles_screen.dart'; +import 'package:givt_app/features/family/features/reflect/presentation/pages/summary_screen.dart'; import 'package:givt_app/features/family/shared/design/components/components.dart'; import 'package:givt_app/features/family/shared/design/illustrations/fun_icon.dart'; import 'package:givt_app/features/family/shared/widgets/texts/shared_texts.dart'; @@ -94,11 +95,8 @@ class _ResultScreenState extends State { ], FunButton.secondary( onTap: () { - Navigator.of(context).popUntil( - ModalRoute.withName( - FamilyPages.profileSelection.name, - ), - ); + Navigator.of(context) + .push(const SummaryScreen().toRoute(context)); }, text: 'Finish reflecting', analyticsEvent: AnalyticsEvent( diff --git a/lib/features/family/features/reflect/presentation/pages/summary_screen.dart b/lib/features/family/features/reflect/presentation/pages/summary_screen.dart new file mode 100644 index 000000000..804e93834 --- /dev/null +++ b/lib/features/family/features/reflect/presentation/pages/summary_screen.dart @@ -0,0 +1,92 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:givt_app/core/enums/amplitude_events.dart'; +import 'package:givt_app/features/family/app/family_pages.dart'; +import 'package:givt_app/features/family/app/injection.dart'; +import 'package:givt_app/features/family/features/reflect/bloc/summary_cubit.dart'; +import 'package:givt_app/features/family/shared/design/components/components.dart'; +import 'package:givt_app/features/family/shared/widgets/buttons/givt_back_button_flat.dart'; +import 'package:givt_app/features/family/shared/widgets/texts/shared_texts.dart'; +import 'package:givt_app/shared/models/analytics_event.dart'; +import 'package:givt_app/shared/widgets/base/base_state_consumer.dart'; +import 'package:givt_app/shared/widgets/fun_scaffold.dart'; + +class SummaryScreen extends StatefulWidget { + const SummaryScreen({super.key}); + + @override + State createState() => _SummaryScreenState(); +} + +class _SummaryScreenState extends State { + final _cubit = SummaryCubit(getIt()); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _cubit.init(); + } + + @override + Widget build(BuildContext context) { + return FunScaffold( + appBar: const FunTopAppBar( + title: 'Awesome work heroes!', + leading: GivtBackButtonFlat(), + ), + body: BaseStateConsumer( + cubit: _cubit, + onData: (context, secretWord) { + return Column( + children: [ + const Spacer(), + const TitleMediumText( + 'Your mission to connect through conversation was a success!', + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + SvgPicture.asset('assets/family/images/family_superheroes.svg'), + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: FunTile.gold( + titleBig: + '${secretWord.minutesPlayed} minutes family time', + iconData: FontAwesomeIcons.solidClock, + assetSize: 32, + ), + ), + const SizedBox(width: 16), + Expanded( + child: FunTile.blue( + titleBig: '${secretWord.questionsAsked} questions asked', + iconData: FontAwesomeIcons.solidCircleQuestion, + assetSize: 32, + ), + ), + ], + ), + const Spacer(), + FunButton( + onTap: () { + Navigator.of(context).popUntil( + ModalRoute.withName( + FamilyPages.profileSelection.name, + ), + ); + }, + text: 'Back to home', + analyticsEvent: AnalyticsEvent( + AmplitudeEvents.familyReflectSummaryBackToHome, + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/features/family/shared/design/components/actions/fun_tile.dart b/lib/features/family/shared/design/components/actions/fun_tile.dart index e92e68c85..019cdc76f 100644 --- a/lib/features/family/shared/design/components/actions/fun_tile.dart +++ b/lib/features/family/shared/design/components/actions/fun_tile.dart @@ -4,129 +4,168 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:givt_app/features/family/shared/widgets/layout/action_container.dart'; import 'package:givt_app/features/family/utils/family_app_theme.dart'; -class FunTile extends StatefulWidget { +class FunTile extends StatelessWidget { const FunTile({ - required this.onTap, required this.borderColor, required this.backgroundColor, required this.textColor, required this.iconPath, + this.onTap, this.isDisabled = false, this.isSelected = false, - this.titleBig = '', - this.titleSmall = '', - this.subtitle = '', + this.titleBig, + this.titleSmall, + this.subtitle, this.assetSize, this.mainAxisAlignment, this.iconData, + this.iconColor, super.key, }); - final VoidCallback onTap; + factory FunTile.gold( + {String? titleBig, + String? titleSmall, + String? subtitle, + IconData? iconData, + VoidCallback? onTap, + double? assetSize}) { + return FunTile( + borderColor: FamilyAppTheme.highlight80, + backgroundColor: FamilyAppTheme.highlight98, + textColor: FamilyAppTheme.highlight40, + iconPath: '', + onTap: onTap, + titleBig: titleBig, + titleSmall: titleSmall, + subtitle: subtitle, + iconData: iconData, + assetSize: assetSize, + iconColor: FamilyAppTheme.info20, + ); + } + + factory FunTile.blue( + {String? titleBig, + String? titleSmall, + String? subtitle, + IconData? iconData, + VoidCallback? onTap, + double? assetSize}) { + return FunTile( + borderColor: FamilyAppTheme.secondary80, + backgroundColor: FamilyAppTheme.secondary98, + textColor: FamilyAppTheme.secondary40, + iconPath: '', + onTap: onTap, + titleBig: titleBig, + titleSmall: titleSmall, + subtitle: subtitle, + iconData: iconData, + assetSize: assetSize, + iconColor: FamilyAppTheme.secondary20, + ); + } + + final VoidCallback? onTap; final Color borderColor; final Color backgroundColor; final Color textColor; final String iconPath; final bool isDisabled; final bool isSelected; - final String titleBig; - final String titleSmall; - final String subtitle; + final String? titleBig; + final String? titleSmall; + final String? subtitle; final double? assetSize; final IconData? iconData; + final Color? iconColor; final MainAxisAlignment? mainAxisAlignment; - @override - State createState() => _FunTileState(); -} - -class _FunTileState extends State { - late Color backgroundColor; - late Color borderColor; - @override Widget build(BuildContext context) { - backgroundColor = widget.backgroundColor; - borderColor = widget.borderColor; - final isOnlineIcon = widget.iconPath.contains('http'); + final isOnlineIcon = iconPath.startsWith('http'); + + var newBackgroundColor = backgroundColor; + var newBorderColor = borderColor; - if (widget.isDisabled) { - backgroundColor = FamilyAppTheme.disabledTileBackground; - borderColor = FamilyAppTheme.disabledTileBorder; + if (isDisabled) { + newBackgroundColor = FamilyAppTheme.disabledTileBackground; + newBorderColor = FamilyAppTheme.disabledTileBorder; } + return ActionContainer( - isDisabled: widget.isDisabled, - isSelected: widget.isSelected, - borderColor: borderColor, - onTap: widget.isDisabled ? () {} : () => widget.onTap(), + isDisabled: isDisabled, + isSelected: isSelected, + borderColor: newBorderColor, + onTap: isDisabled ? () {} : onTap, child: Stack( children: [ Container( - color: backgroundColor, + color: newBackgroundColor, width: double.infinity, child: Column( - mainAxisAlignment: - widget.mainAxisAlignment ?? MainAxisAlignment.start, + mainAxisAlignment: mainAxisAlignment ?? MainAxisAlignment.start, children: [ - const SizedBox(height: 10), + SizedBox(height: iconData != null ? 24 : 10), Opacity( - opacity: widget.isDisabled ? 0.5 : 1, - child: widget.iconData == null + opacity: isDisabled ? 0.5 : 1, + child: iconData == null ? isOnlineIcon ? SvgPicture.network( - widget.iconPath, - height: widget.assetSize ?? 140, - width: widget.assetSize ?? 140, + iconPath, + height: assetSize ?? 140, + width: assetSize ?? 140, ) : SvgPicture.asset( - widget.iconPath, - height: widget.assetSize ?? 140, - width: widget.assetSize ?? 140, + iconPath, + height: assetSize ?? 140, + width: assetSize ?? 140, ) : FaIcon( - widget.iconData, - size: widget.assetSize ?? 140, - color: widget.textColor.withOpacity(0.6), + iconData, + size: assetSize ?? 140, + color: iconColor ?? textColor.withOpacity(0.6), ), ), Padding( padding: const EdgeInsets.fromLTRB(10, 8, 10, 16), child: Column( children: [ - if (widget.titleBig.isNotEmpty) + if (titleBig != null) Text( - widget.titleBig, + titleBig!, textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: widget.isDisabled + color: isDisabled ? FamilyAppTheme.disabledTileBorder - : widget.textColor, + : textColor, ), ) else const SizedBox(), - if (widget.titleSmall.isNotEmpty) + if (titleSmall != null) Text( - widget.titleSmall, + titleSmall!, textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelMedium?.copyWith( - color: widget.isDisabled + color: isDisabled ? FamilyAppTheme.disabledTileBorder - : widget.textColor, + : textColor, ), ) else const SizedBox(), - SizedBox(height: widget.subtitle.isNotEmpty ? 8 : 0), - if (widget.subtitle.isNotEmpty) + const SizedBox(height: 8), + if (subtitle != null) Text( - widget.subtitle, + subtitle!, textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelMedium?.copyWith( - color: widget.textColor.withAlpha(200), + color: textColor.withAlpha(200), ), ) else @@ -137,7 +176,7 @@ class _FunTileState extends State { ], ), ), - if (widget.isSelected) + if (isSelected) Positioned( top: 0, right: 0, diff --git a/lib/features/family/shared/widgets/layout/action_container.dart b/lib/features/family/shared/widgets/layout/action_container.dart index c0b6e959b..a49415733 100644 --- a/lib/features/family/shared/widgets/layout/action_container.dart +++ b/lib/features/family/shared/widgets/layout/action_container.dart @@ -5,8 +5,8 @@ import 'package:givt_app/features/family/utils/family_app_theme.dart'; class ActionContainer extends StatefulWidget { const ActionContainer({ required this.borderColor, - required this.onTap, required this.child, + this.onTap, super.key, this.isDisabled = false, this.isSelected = false, @@ -15,7 +15,7 @@ class ActionContainer extends StatefulWidget { this.borderSize = 2, this.baseBorderSize = 6, }); - final VoidCallback onTap; + final VoidCallback? onTap; final bool isDisabled; final bool isSelected; final Color borderColor; @@ -39,7 +39,10 @@ class _ActionContainerState extends State { bool _isManualPressed = false; bool get _isPressed { - return _isManualPressed || widget.isDisabled || widget.isSelected; + return _isManualPressed || + widget.isDisabled || + widget.isSelected || + widget.onTap == null; } void _setManualPressed(bool value) { @@ -53,12 +56,12 @@ class _ActionContainerState extends State { borderColor = widget.isDisabled ? FamilyAppTheme.disabledTileBorder : widget.borderColor; - return widget.isDisabled + return widget.isDisabled || widget.onTap == null ? _buildContainer(widget.child) : GestureDetector( onTap: () async { await _actionDelay(); - widget.onTap(); + widget.onTap!(); }, onTapDown: (details) { SystemSound.play(SystemSoundType.click);