Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: After game summary hackathon #1290

Merged
merged 53 commits into from
Nov 27, 2024
Merged

Feature: After game summary hackathon #1290

merged 53 commits into from
Nov 27, 2024

Conversation

Daniela510
Copy link
Contributor

@Daniela510 Daniela510 commented Nov 27, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Added multiple launch configurations for different environments (development, staging, production).
    • Introduced a new screen for assigning bedtime responsibilities.
    • Integrated audio recording and playback functionalities.
    • Expanded the gratitude summary features with new models and UI components.
  • Bug Fixes

    • Enhanced error handling in various methods related to notifications and API interactions.
  • Documentation

    • Updated dependency versions to include new audio libraries.
  • Chores

    • Cleaned up import statements and refactored existing code for better readability and maintainability.

TammiLion and others added 30 commits November 19, 2024 16:08
Feature: Implement yes no screen without design for now (KIDS-1686)
…ssion

Feature: create game session (KIDS-1693)
Feature: fetch summary api call (KIDS-1700)
# Conflicts:
#	lib/core/enums/amplitude_events.dart
…-aos-donate-calls

Feature: Add game guid to pledge aos (KIDS-1699)
…mmary

Feature: show latest summary with placeholder ui (KIDS-1701)
…-donation

Feature: pass gameid on donation part 2 (KIDS-1699)
Feature: Notification service passes param to route
@Daniela510 Daniela510 requested a review from a team as a code owner November 27, 2024 11:21
Copy link
Contributor

coderabbitai bot commented Nov 27, 2024

Walkthrough

The pull request introduces several enhancements across multiple files, primarily focusing on the addition of new configurations for launching a Dart application in various environments (development, staging, production), and new permissions for audio recording in both Android and iOS platforms. Additionally, it expands the functionality related to gratitude summaries, including new UI components, data models, and state management classes for handling bedtime responsibilities and audio recording features.

Changes

File Path Change Summary
.vscode/launch.json Added configurations for "Launch development," "Launch staging," "Launch production," and "Golden" with specific arguments for each.
android/app/src/main/AndroidManifest.xml Added permissions for audio recording: RECORD_AUDIO, MODIFY_AUDIO_SETTINGS, and WRITE_EXTERNAL_STORAGE.
ios/Runner/Info.plist Added NSMicrophoneUsageDescription key for microphone access permission.
lib/core/enums/amplitude_events.dart Added new enum values for gratitude and bedtime functionalities.
lib/core/notification/notification_service.dart Updated notification handling logic, added logging, and refined scheduling methods.
lib/features/family/app/family_pages.dart Added new entries for assignBedtimeResponsibility and parentSummary in the FamilyPages enum.
lib/features/family/app/family_routes.dart Introduced new routes for AssignBedtimeResponsibilityScreen and ParentSummaryScreen.
lib/features/family/app/injection.dart Registered new ParentSummaryCubit and ParentSummaryRepository in the dependency injection setup.
lib/features/family/features/giving_flow/create_transaction/models/transaction.dart Added optional gameGuid parameter to Transaction class.
lib/features/family/features/giving_flow/screens/choose_amount_slider_screen.dart Updated gameGuid assignment based on isActOfService.
lib/features/family/features/gratitude-summary/bloc/parent_summary_cubit.dart Introduced ParentSummaryCubit for managing summary state.
lib/features/family/features/gratitude-summary/data/models/parent_summary_response.dart Added ParentSummaryResponse class to handle summary data.
lib/features/family/features/gratitude-summary/data/record_utils.dart Introduced AudioRecorderMixin for audio recording functionality.
lib/features/family/features/gratitude-summary/domain/models/parent_summary_item.dart Added ParentSummaryItem and Conversation classes for summary data handling.
lib/features/family/features/gratitude-summary/domain/repositories/parent_summary_repository.dart Created ParentSummaryRepository for managing API interactions.
lib/features/family/features/gratitude-summary/presentation/models/parent_summary_uimodel.dart Added ParentSummaryUIModel and ConversationUIModel classes for UI representation.
lib/features/family/features/gratitude-summary/presentation/pages/parent_summary_screen.dart Introduced ParentSummaryScreen for displaying summary data.
lib/features/family/features/gratitude-summary/presentation/pages/record_summary_message_.bottomsheet.dart Added RecordSummaryMessageBottomsheet for recording audio messages.
lib/features/family/features/gratitude-summary/presentation/widgets/audio_player.dart Introduced AudioPlayer widget for audio playback.
lib/features/family/features/gratitude-summary/presentation/widgets/audio_recorder.dart Added Recorder widget for audio recording functionality.
lib/features/family/features/gratitude-summary/presentation/widgets/summary_conversation_item.dart Introduced SummaryConversationItem for displaying conversation summaries.
lib/features/family/features/gratitude-summary/presentation/widgets/summary_conversation_list.dart Added SummaryConversationList for displaying lists of conversations.
lib/features/family/features/home_screen/presentation/pages/family_home_screen.dart Added button for navigating to "Show Latest Summary" page with analytics logging.
lib/features/family/features/parent_giving_flow/cubit/give_cubit.dart Updated GiveCubit to include ReflectAndShareRepository for game identification.
lib/features/family/features/reflect/bloc/grateful_cubit.dart Modified saveActOfService method to include game ID.
lib/features/family/features/reflect/bloc/summary_cubit.dart Added new state management features and methods for summary details.
lib/features/family/features/reflect/domain/grateful_recommendations_repository.dart Updated savePledge method to include optional gameGuid parameter.
lib/features/family/features/reflect/domain/grateful_recommendations_repository_impl.dart Enhanced savePledge method to conditionally include gameGuid in requests.
lib/features/family/features/reflect/domain/models/summary_details.dart Modified SummaryDetails class to include new properties for audio and missing adults.
lib/features/family/features/reflect/domain/reflect_and_share_repository.dart Added methods for managing game sessions and audio sharing.
lib/features/family/features/reflect/presentation/pages/record_answer_screen.dart Removed unused SVG import.
lib/features/family/features/reflect/presentation/pages/summary_screen.dart Updated UI to reflect new data structure and added conditional rendering for audio components.
lib/features/family/network/family_api_service.dart Introduced methods for uploading audio files and managing game data.
lib/features/family/shared/design/components/actions/fun_tile.dart Updated FunTile class to include isPressedDown parameter for state management.
lib/features/family/shared/design/components/navigation/fun_top_app_bar.dart Added titleColor parameter to FunTopAppBar for customizable title color.
lib/features/family/shared/design/illustrations/fun_icon.dart Enhanced FunIcon class with new parameters for icon customization.
lib/features/family/utils/family_app_theme.dart Added new color constants to FamilyAppTheme.
pubspec.yaml Updated app version to 4.7.1 and added dependencies for record and audioplayers.
lib/features/family/features/gratitude-summary/bloc/bedtime_responsibility_cubit.dart Introduced AssignBedtimeResponsibilityCubit for managing bedtime assignments.
lib/features/family/features/gratitude-summary/presentation/pages/bedtime_reponsibility_screen.dart Added AssignBedtimeResponsibilityScreen for assigning bedtime responsibilities.

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • TammiLion

🐇 In the meadow, changes bloom,
A launch for devs, dispelling gloom.
With audio rights and routes anew,
Our app takes flight, with joy in view!
So hop along, let’s celebrate,
For every change, we elevate! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

# Conflicts:
#	lib/features/family/features/home_screen/presentation/pages/family_home_screen.dart
#	lib/features/family/features/reflect/domain/reflect_and_share_repository.dart
#	lib/features/family/features/reflect/presentation/pages/summary_screen.dart
#	lib/features/family/network/family_api_service.dart
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 48

🧹 Outside diff range and nitpick comments (62)
lib/features/family/features/reflect/domain/models/summary_details.dart (3)

4-9: Add documentation for the class and constructor parameters.

While the constructor is well-structured with appropriate default values, adding documentation would improve code maintainability and make it clearer how this class should be used.

Consider adding documentation like this:

+/// Details of a game summary including play time, generous deeds, missing participants, and audio recording.
 class SummaryDetails {
+  /// Creates a new summary details instance.
+  /// 
+  /// [minutesPlayed] represents the total time spent playing.
+  /// [generousDeeds] represents the number of generous actions performed.
+  /// [missingAdults] optional list of adults who haven't played yet.
+  /// [audioPath] optional path to an audio recording associated with this summary.
   SummaryDetails({
     required this.minutesPlayed,
     required this.generousDeeds,
     this.missingAdults = const [],
     this.audioPath = '',
   });

11-14: Consider making fields final and adding documentation.

Since this class represents a summary (a snapshot of state), these fields should likely be immutable after construction.

Consider applying these improvements:

-  int minutesPlayed;
-  int generousDeeds;
-  List<Profile> missingAdults;
-  String audioPath;
+  /// The total minutes spent playing.
+  final int minutesPlayed;
+  /// The number of generous deeds performed.
+  final int generousDeeds;
+  /// List of adults who haven't participated yet.
+  final List<Profile> missingAdults;
+  /// Path to the audio recording associated with this summary.
+  final String audioPath;

16-18: Improve property names and add documentation.

The current implementation of adultName might be confusing as it returns 'the family' in two different cases (empty list or multiple adults). Also, the name allAdultsPlayed could be clearer.

Consider these improvements:

-  String get adultName =>
-      missingAdults.length == 1 ? missingAdults.first.firstName : 'the family';
-  bool get allAdultsPlayed => missingAdults.isEmpty;
+  /// Returns the name of the single missing adult or 'the family' if there are
+  /// multiple missing adults or no missing adults.
+  String get missingAdultName {
+    if (missingAdults.isEmpty) return 'the family';
+    if (missingAdults.length == 1) return missingAdults.first.firstName;
+    return 'the family';
+  }
+
+  /// Returns true if there are no missing adults (everyone has played).
+  bool get hasAllAdultsPlayed => missingAdults.isEmpty;
lib/features/family/features/giving_flow/create_transaction/models/transaction.dart (1)

8-8: Consider adding GUID format validation

The gameGuid field accepts any string value, but if it's expected to be a GUID/UUID, consider adding validation to ensure the correct format.

Consider adding a validator method or using a UUID package to validate the format:

bool _isValidUuid(String? uuid) {
  if (uuid == null) return true;
  final RegExp uuidRegExp = RegExp(
    r'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
    caseSensitive: false,
  );
  return uuidRegExp.hasMatch(uuid);
}

Then use it in the constructor:

assert(gameGuid == null || _isValidUuid(gameGuid), 'Invalid GUID format');

Also applies to: 16-16

lib/features/family/features/reflect/domain/grateful_recommendations_repository.dart (1)

20-20: Add documentation for the gameGuid parameter

While the parameter addition is good, consider adding documentation to explain:

  • The purpose of the gameGuid parameter
  • When it should be provided vs. omitted
  • Any specific format requirements

Example documentation:

  Future<void> savePledge(
    GameProfile profile,
    Organisation organisation,
+   /// The unique identifier of the game session.
+   /// Should be provided when the pledge is made during a game session.
+   /// Format: UUID v4
    String? gameGuid,
  );
lib/features/family/features/gratitude-summary/domain/repositories/parent_summary_repository.dart (1)

4-24: Good architectural design with room for enhancement.

The repository follows clean architecture principles well. Consider these enhancements:

  1. Add repository interface/abstract class for better testability and dependency inversion
  2. Consider adding local caching for game summaries to improve performance
  3. Add retry logic for network operations

Example interface implementation:

abstract class IParentSummaryRepository {
  Future<bool> sendYesNoPutKidToBed({
    required String childGuid,
    required String parentGuid,
    required bool didPutToBed,
  });
  Future<ParentSummaryItem> fetchLatestGameSummary();
}

class ParentSummaryRepository implements IParentSummaryRepository {
  // ... existing implementation
}
lib/features/family/features/gratitude-summary/data/models/parent_summary_response.dart (2)

1-10: Add documentation for the class and its purpose

Consider adding documentation comments to describe the purpose of this class and its role in the gratitude summary feature.

+/// Represents the response model for a parent's gratitude summary.
+/// 
+/// This model contains essential information about a summary including its
+/// identifier, content, associated image, and family member details.
 class ParentSummaryResponse {

22-27: Consider adding a toJson method and using DateTime for date field

  1. Add a toJson method for serialization consistency
  2. Use DateTime instead of String for the date field
-  final String date;
+  final DateTime date;

+  Map<String, dynamic> toJson() => {
+    'id': id,
+    'title': title,
+    'description': description,
+    'imageUrl': imageUrl,
+    'date': date.toIso8601String(),
+    'familyMemberId': familyMemberId,
+  };
lib/features/family/features/gratitude-summary/presentation/widgets/summary_conversation_list.dart (1)

11-22: Consider performance implications of shrinkWrap

While the ListView implementation is generally good, using shrinkWrap: true without constraining the scroll physics could lead to performance issues if this widget is nested inside another scrollable view.

Consider one of these improvements:

  1. If this list is meant to scroll independently:
 return ListView.builder(
   shrinkWrap: true,
+  physics: const ClampingScrollPhysics(),
   itemBuilder: (context, index) {
  1. If this list is meant to be part of a larger scrollable:
 return ListView.builder(
   shrinkWrap: true,
+  physics: const NeverScrollableScrollPhysics(),
   itemBuilder: (context, index) {
lib/features/family/features/gratitude-summary/data/record_utils.dart (1)

8-44: Consider improving architecture and testability

  1. The mixin approach might make testing difficult. Consider:
    • Creating an abstract interface for recording functionality
    • Implementing a concrete recorder class
    • Using dependency injection for better testability
  2. Add proper lifecycle management for recording sessions
  3. Consider implementing a repository pattern for managing recorded files

Example interface and implementation structure:

abstract class AudioRecorder {
  Future<void> recordFile(RecordConfig config);
  Future<void> recordStream(RecordConfig config);
  Future<void> stop();
}

class AudioRecorderImpl implements AudioRecorder {
  final Record _recorder;
  final AudioFileRepository _repository;

  AudioRecorderImpl(this._recorder, this._repository);
  
  // Implementation details...
}

abstract class AudioFileRepository {
  Future<String> generateFilePath();
  Future<void> saveRecording(List<int> data);
  Future<void> deleteRecording(String path);
}
lib/features/family/features/gratitude-summary/bloc/yesno-cubit.dart (1)

7-8: Consider using a more specific type parameter instead of dynamic

The first type parameter of CommonCubit<dynamic, bool> is using dynamic, which bypasses type safety. Consider using a more specific type that represents your input data, or void if no input data is needed.

-class YesNoCubit extends CommonCubit<dynamic, bool> {
+class YesNoCubit extends CommonCubit<void, bool> {
lib/features/family/features/reflect/bloc/summary_cubit.dart (2)

12-14: Add documentation for the new fields

Consider adding documentation comments to explain the purpose and expected values of these fields, especially since they represent important metrics in the game summary.

+  /// Total minutes spent playing the game, rounded up from seconds
   int _totalMinutesPlayed = 0;
+  /// Number of generous deeds performed during the game
   int _generousDeeds = 0;
+  /// List of adult profiles who haven't played yet
   List<Profile> _missingAdults = const [];

17-19: Extract time conversion logic

Consider extracting the seconds to minutes conversion logic into a separate method for better readability and reusability.

+  int _convertSecondsToMinutes(int seconds) {
+    return (seconds / 60).ceil();
+  }
+
   void init() {
-    _totalMinutesPlayed =
-        (_reflectAndShareRepository.totalTimeSpentInSeconds / 60).ceil();
+    _totalMinutesPlayed = _convertSecondsToMinutes(
+        _reflectAndShareRepository.totalTimeSpentInSeconds);
lib/features/family/features/gratitude-summary/domain/models/parent_summary_item.dart (1)

1-63: Great architectural design with clear separation of concerns!

The separation between domain models (ParentSummaryItem, Conversation) and UI models (through toUIModel() methods) provides good flexibility and maintainability. This pattern will make it easier to:

  • Modify the UI representation without affecting the domain logic
  • Add new data transformations in the future
  • Test the models independently
lib/features/family/shared/design/components/navigation/fun_top_app_bar.dart (2)

57-60: Add documentation for the title color customization

Consider adding a documentation comment explaining the purpose and usage of the titleColor parameter, especially noting its default value and relationship with the app's theme.

+ /// The color of the app bar title. If not specified, defaults to [FamilyAppTheme.primary30].
  final Color? titleColor;

Line range hint 16-41: Consider exposing titleColor in factory constructors

The factory constructors .primary99 and .white don't expose the titleColor parameter, which might limit their usefulness in scenarios where title color customization is needed.

  factory FunTopAppBar.primary99({
    required String title,
    List<Widget> actions = const [],
    Widget? leading,
    Color? systemNavigationBarColor,
+   Color? titleColor,
  }) =>
      FunTopAppBar(
        title: title,
        actions: actions,
        leading: leading,
        color: FamilyAppTheme.primary99,
        systemNavigationBarColor: systemNavigationBarColor,
+       titleColor: titleColor,
      );

  factory FunTopAppBar.white({
    required String title,
    List<Widget> actions = const [],
    Widget? leading,
    Color? systemNavigationBarColor,
+   Color? titleColor,
  }) =>
      FunTopAppBar(
        title: title,
        actions: actions,
        leading: leading,
        color: Colors.white,
        systemNavigationBarColor: systemNavigationBarColor,
+       titleColor: titleColor,
      );
lib/features/family/features/parent_giving_flow/cubit/give_cubit.dart (1)

Line range hint 23-40: Document the integration between giving and gaming features

The createTransaction method now couples giving functionality with gaming features through the gameGuid. Consider:

  1. Adding documentation explaining this integration
  2. Creating an ADR (Architecture Decision Record) to document this design decision
  3. Consider if this coupling should be managed at a higher architectural level

This will help future maintainers understand the relationship between these features.

lib/features/family/features/gratitude-summary/presentation/widgets/summary_conversation_item.dart (2)

11-11: Improve consistency in size declarations

The static size constant is only used for SVG dimensions. Consider using it consistently throughout the widget for better maintainability.

 static const int size = 80;
 ...
               child: SvgPicture.network(
                 uiModel.profilePicture,
-                width: 80,
-                height: 80,
+                width: size.toDouble(),
+                height: size.toDouble(),
               ),

Also applies to: 43-44


16-24: Consider extracting style constants

The hardcoded spacing, border radius, and padding values should be moved to the theme or constants for better maintainability and consistency across the app.

Consider moving these values to FamilyAppTheme:

// In FamilyAppTheme
+ static const double summaryItemBorderRadius = 12.0;
+ static const double summaryItemPadding = 12.0;
+ static const EdgeInsets summaryItemMargin = EdgeInsets.only(bottom: 16, left: 16, right: 16);

// In this file
      margin: const EdgeInsets.only(bottom: 16, left: 16, right: 16),
-      borderRadius: BorderRadius.circular(12),
+      borderRadius: BorderRadius.circular(FamilyAppTheme.summaryItemBorderRadius),
-      padding: const EdgeInsets.all(12),
+      padding: const EdgeInsets.all(FamilyAppTheme.summaryItemPadding),
lib/features/family/app/family_pages.dart (1)

25-26: LGTM with suggestions for improved maintainability

The new enum entries are correctly implemented and follow the existing patterns. However, consider these improvements:

  1. Consider a more descriptive name for yesNo to better indicate its purpose in the game summary context
  2. Add a comment section to group these game-related routes together, similar to how other features are grouped (e.g., "//coin flow", "//school event mode")
+ //game summary
  yesNo(path: 'yes-no', name: 'YES-NO'),
  parentSummary(path: 'parent-summary', name: 'PARENT-SUMMARY'),
.vscode/launch.json (3)

Line range hint 7-57: Development and Staging environments should be distinct

The staging configuration currently uses the same development endpoints and API keys as the development environment. Staging should typically mirror production configuration with its own dedicated endpoints and keys to properly validate changes before production deployment.

Consider:

  • Using separate API endpoints for staging
  • Using distinct API keys for better isolation
  • Ensuring staging environment closely mirrors production setup

Line range hint 58-89: SECURITY RISK: Production credentials should not be committed to version control

The configuration contains sensitive production credentials including:

  • Live Stripe API keys
  • Production API endpoints
  • Analytics keys

This poses a significant security risk as these credentials could be misused if the repository is compromised.

Recommended actions:

  1. Immediately rotate all exposed credentials
  2. Move sensitive values to environment variables or a secure configuration management system
  3. Update the launch configuration to use placeholders:
 "--dart-define",
-"STRIPE_PK=pk_live_51NGl2uLM5mQffuXIGIt8OSH7HWeo2lqPLyvMJSi3ypUDH7ITn84S4A8sGoFvXpP4DCtoUQFlNdWm5jraqR8Ce0sE00Nb4wDuBW",
+"STRIPE_PK=${STRIPE_PK}",

Line range hint 1-6: Consider adding additional launch configurations

The current setup could be enhanced with:

  • Release mode configurations for performance testing
  • Platform-specific configurations (iOS/Android)
  • Profile mode for performance profiling

Example addition:

{
  "name": "Launch production (release)",
  "request": "launch",
  "type": "dart",
  "flutterMode": "release",
  "program": "lib/main_production.dart",
  "args": [
    "--flavor",
    "production",
    "--target",
    "lib/main_production.dart"
  ]
}
lib/features/family/features/gratitude-summary/presentation/pages/record_summary_message_.bottomsheet.dart (2)

28-31: Remove debug print statement.

The print statement should be removed from production code. If logging is needed, consider using a proper logging framework.

-        // ignore: avoid_print
-        onDone(value);
-        print('Recorded message path: $value');
+        onDone(value);

52-92: Consider enhancing error handling and loading states.

The current implementation could benefit from:

  1. Error handling for recording failures
  2. Loading states during recording initialization
  3. Memory management for audio resources
  4. Permission handling for microphone access

This would improve the robustness and user experience of the feature.

lib/core/enums/country.dart (1)

Line range hint 78-81: Consider addressing the TODO comment for localization

The comment suggests a more maintainable approach to handling country localizations. This would be a good opportunity to implement this improvement while working on country-related changes.

Would you like me to help implement the suggested localization improvement or create a GitHub issue to track this task?

lib/features/family/features/gratitude-summary/presentation/pages/yesno_screen.dart (3)

1-3: Remove unused import

The flutter_svg package is imported but not used in this file.

-import 'package:flutter_svg/flutter_svg.dart';

36-43: Consider making text final in initState

Since text is initialized immediately in initState, it doesn't need to be marked as late. Additionally, consider making it final and using a separate variable for the updated text in setState.

-  late String text;
+  String _text = '';

  @override
  void initState() {
    super.initState();
    _cubit.init(widget.childGuid);
-    text = 'Are you putting ${widget.name} to bed?';
+    _text = 'Are you putting ${widget.name} to bed?';
  }

83-118: Improve layout constants and spacing

Consider the following improvements:

  1. Remove the empty onTap callback from AvatarWidget if it's not needed
  2. Extract the magic number 100 to a named constant
  3. Consider using fixed padding instead of Spacer for more consistent spacing across different screen sizes
+  static const _avatarSize = 100.0;
+
   Widget body(String text) => Column(
         children: [
-          const Spacer(),
+          const SizedBox(height: 48),
           AvatarWidget(
               uiModel: AvatarUIModel(
                 avatarUrl: widget.imageUrl,
                 text: widget.name,
               ),
-              circleSize: 100,
+              circleSize: _avatarSize,
               textColor: Colors.transparent,
-              onTap: () {}),
+              onTap: null),
           TitleMediumText(
             text,
             textAlign: TextAlign.center,
             color: Colors.white,
           ),
-          const Spacer(),
+          const SizedBox(height: 48),
lib/features/family/features/gratitude-summary/presentation/pages/parent_summary_screen.dart (1)

44-108: Add accessibility support for better user experience.

The UI lacks semantic labels and accessibility features that would make it more inclusive for users with disabilities.

Consider these improvements:

 GestureDetector(
   onTap: () async {
     // ... existing code
   },
   child: primaryCircleWithIcon(
     iconData: Icons.play_arrow,
+    semanticsLabel: _isPlaying 
+      ? 'Pause audio summary' 
+      : 'Play audio summary',
   ),
 ),
 FunButton(
   isDisabled: uiModel?.audioLink != null && !_hasClickedAudio,
   text: 'Done',
+  semanticsLabel: 'Close summary screen',
   onTap: () {
     context.pop();
   },
   // ... rest of the code
 ),
pubspec.yaml (1)

82-83: Document the purpose of new dependencies

Consider adding comments above these new dependencies to explain their purpose in the context of the "After game summary" feature, similar to how other dependencies are documented in this file.

+  # For recording audio in after game summaries
   record: ^5.2.0
+  # For playing recorded audio in after game summaries
   audioplayers: ^6.1.0
lib/features/family/features/gratitude-summary/bloc/parent_summary_cubit.dart (1)

106-112: Consider adding loading state during retry.

The retry mechanism works but doesn't show loading state to users. Consider updating the UI state during retry operations.

  Future<void> onClickTryAgain() async {
+   emitLoading();
    await fetchSummary();
  }
lib/features/family/shared/design/illustrations/fun_icon.dart (2)

54-63: Fix parameter naming convention

The parameter iconsize should follow the camelCase naming convention to match the class property iconSize.

Apply this diff to fix the naming:

   factory FunIcon.microphone(
           {Color circleColor = FamilyAppTheme.primary95,
-          double iconsize = 48,
+          double iconSize = 48,
           double circleSize = 112}) =>
       FunIcon(
         iconData: FontAwesomeIcons.microphone,
         circleColor: circleColor,
-        iconSize: iconsize,
+        iconSize: iconSize,
         circleSize: circleSize,
       );

64-74: Fix parameter naming convention

The parameter iconsize should follow the camelCase naming convention to match the class property iconSize.

Apply this diff to fix the naming:

   factory FunIcon.recordingSquare(
           {Color circleColor = FamilyAppTheme.error90,
           Color iconColor = FamilyAppTheme.error30,
-          double iconsize = 48,
+          double iconSize = 48,
           double circleSize = 112}) =>
       FunIcon(
         iconData: FontAwesomeIcons.solidSquare,
         iconColor: iconColor,
         circleColor: circleColor,
-        iconSize: iconsize,
+        iconSize: iconSize,
         circleSize: circleSize,
       );
lib/features/family/features/reflect/domain/grateful_recommendations_repository_impl.dart (1)

151-156: Consider API field naming convention and code improvements.

A few suggestions for the API payload modifications:

  1. The 'GameId' field uses Pascal case which is uncommon in JSON APIs. Consider using snake_case ('game_id') or camelCase ('gameId') for consistency.
  2. The field name could be extracted to a constant to avoid duplication and ensure consistency.
  3. Consider adding validation for the gameGuid format.

Here's a suggested improvement:

+ const String _gameIdField = 'game_id';  // or 'gameId'

  @override
  Future<void> savePledge(
    GameProfile profile,
    Organisation organisation,
    String? gameGuid,
  ) async {
+   if (gameGuid != null && !_isValidGameGuid(gameGuid)) {
+     throw ArgumentError('Invalid game GUID format');
+   }
    
    organisation.type == RecommendationTypes.organisation
        ? await _familyApiService.savePledge({
            "userId": profile.userId,
            "collectGroupId": organisation.guid,
-           if (gameGuid != null) 'GameId': gameGuid,
+           if (gameGuid != null) _gameIdField: gameGuid,
          })
        : await _familyApiService.savePledge({
            "userId": profile.userId,
            "actOfServiceId": organisation.guid,
-           if (gameGuid != null) 'GameId': gameGuid,
+           if (gameGuid != null) _gameIdField: gameGuid,
          });
  }

+ bool _isValidGameGuid(String guid) {
+   // Add GUID validation logic here
+   return true;
+ }
lib/features/family/features/reflect/presentation/pages/summary_screen.dart (3)

8-8: Fix the file naming convention

The filename record_summary_message_.bottomsheet.dart has an unusual underscore before the .bottomsheet extension. Consider renaming it to record_summary_message_bottomsheet.dart to maintain consistent naming conventions.


90-104: Add error handling for audio recording

While the recording UI implementation is good, consider adding error handling for potential recording failures (e.g., microphone permissions denied, device errors).

 onTap: () {
+  try {
     RecordSummaryMessageBottomsheet.show(
       context,
       details.adultName,
       _cubit.audioAvailable,
     );
+  } catch (e) {
+    ScaffoldMessenger.of(context).showSnackBar(
+      SnackBar(content: Text('Failed to start recording: $e')),
+    );
+  }
 },

Remove isPressedDown: true from FunTile widgets

The isPressedDown property in ActionContainer is meant to show a permanent pressed state, but it conflicts with the interactive nature of these tiles. The code shows that:

  1. Both tiles have analytics events, indicating they should be interactive
  2. ActionContainer provides proper tap feedback with haptics and visual states
  3. Setting isPressedDown: true permanently disables this visual feedback by forcing a pressed state

Remove isPressedDown: true from both FunTile.gold and FunTile.red widgets to restore proper interactive visual feedback while maintaining their clickable behavior.

🔗 Analysis chain

Line range hint 60-84: Review the permanent pressed state of FunTile widgets

Both tiles have isPressedDown: true which makes them permanently appear pressed, while they're still clickable (as evidenced by the analytics events). This might confuse users about the interactivity of these elements.

Consider either:

  1. Remove isPressedDown if these should be regular interactive tiles
  2. Remove the analytics events if these should be non-interactive pressed tiles

From the output, I can see that FunTile factory constructors have isPressedDown as an optional parameter defaulting to false. Let's check for other usages of FunTile to see the typical pattern.


Let's check the FunTile widget implementation to understand how isPressedDown and onTap interact.


Let's check the ActionContainer implementation to fully understand how isPressedDown affects the interaction.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if FunTile is typically used with permanent isPressedDown
ast-grep --pattern 'FunTile.$_($$$isPressedDown: true$$$)'

Length of output: 60


Script:

#!/bin/bash
# Let's try a different approach to find FunTile usages
rg "FunTile\." -A 10 -B 2

Length of output: 6696


Script:

#!/bin/bash
# Search for other FunTile usages with context
rg "child:\s+FunTile\." -A 8

Length of output: 2314


Script:

#!/bin/bash
# Get the FunTile implementation
cat lib/features/family/shared/design/components/actions/fun_tile.dart

Length of output: 8894


Script:

#!/bin/bash
# Get the ActionContainer implementation
cat lib/shared/widgets/action_container.dart

Length of output: 6285

lib/features/family/features/gratitude-summary/presentation/widgets/audio_recorder.dart (5)

19-44: Consider making the amplitude interval configurable.

The amplitude check interval is hardcoded to 300ms. Consider making this configurable through a widget parameter to allow for different use cases.

 class Recorder extends StatefulWidget {
-  const Recorder({super.key, required this.onStop});
+  const Recorder({
+    super.key,
+    required this.onStop,
+    this.amplitudeInterval = const Duration(milliseconds: 300),
+  });
   final void Function(String path) onStop;
+  final Duration amplitudeInterval;

62-63: Remove commented-out code.

The commented stream recording code should be removed if it's not being used.

-        // Record to stream
-        // await recordStream(_audioRecorder, config);

134-139: Remove commented-out amplitude visualization code.

If the amplitude visualization is not being used, remove the commented code. If it's planned for future use, track it with a TODO comment or create an issue.

-        // if (_amplitude != null) ...[
-        //   const SizedBox(height: 40),
-        //   Text('Current: ${_amplitude?.current ?? 0.0}'),
-        //   Text('Max: ${_amplitude?.max ?? 0.0}'),
-        // ],

167-174: Enhance tap target size for better accessibility.

The current tap target might be too small for comfortable interaction. Consider increasing the minimum size to 48x48 logical pixels as per Material Design guidelines.

 return InkWell(
   borderRadius: BorderRadius.circular(60),
-  child: SizedBox(child: icon),
+  child: SizedBox(
+    width: 48,
+    height: 48,
+    child: Center(child: icon),
+  ),
   onTap: () {
     (_recordState != RecordState.stop) ? _stop() : _start();
   },
 );

194-201: Simplify number formatting using string padding.

The number formatting logic can be simplified using Dart's string padding.

 String _formatNumber(int number) {
-  String numberStr = number.toString();
-  if (number < 10) {
-    numberStr = '0$numberStr';
-  }
-  return numberStr;
+  return number.toString().padLeft(2, '0');
 }
lib/features/family/features/gratitude-summary/presentation/widgets/audio_player.dart (4)

4-5: Remove duplicate import of audioplayers package.

The package is imported twice, once with an alias and once without. Consolidate these imports into a single import with the alias.

-import 'package:audioplayers/audioplayers.dart' as ap;
-import 'package:audioplayers/audioplayers.dart';
+import 'package:audioplayers/audioplayers.dart' as ap;

102-102: Remove commented code.

Either implement the duration display or remove the commented code to maintain cleaner source files.

-            //   Text('00:${_duration?.inSeconds ?? 0.0}'),

85-99: Add confirmation dialog before deletion.

Consider adding a confirmation dialog before deleting the audio file to prevent accidental deletions.

 IconButton(
   icon: const Icon(Icons.delete,
       color: FamilyAppTheme.neutral70, size: _deleteBtnSize),
   onPressed: () {
+    showDialog(
+      context: context,
+      builder: (context) => AlertDialog(
+        title: const Text('Delete Recording'),
+        content: const Text('Are you sure you want to delete this recording?'),
+        actions: [
+          TextButton(
+            onPressed: () => Navigator.pop(context),
+            child: const Text('Cancel'),
+          ),
+          TextButton(
+            onPressed: () {
+              Navigator.pop(context);
               if (_audioPlayer.state == ap.PlayerState.playing) {
                 stop().then((value) {
                   _deleteFile();
                   widget.onDelete();
                 });
               } else {
                 _deleteFile();
                 widget.onDelete();
               }
+            },
+            child: const Text('Delete'),
+          ),
+        ],
+      ),
+    );
   },
 ),

1-195: Consider extracting audio handling logic into a separate service.

While the current implementation works well, the widget is handling both UI and audio business logic. Consider extracting the audio handling logic into a separate service class for better separation of concerns and reusability.

This would:

  • Make the widget more focused on UI rendering
  • Allow for easier testing of audio logic
  • Enable reuse of audio functionality across different widgets
  • Simplify state management

Would you like me to provide an example of how to implement this separation?

lib/features/family/features/reflect/bloc/grateful_cubit.dart (2)

166-169: Consider wrapping savePledge call in try-catch

While the outer try-catch will catch errors, consider handling savePledge failures separately to provide more specific error feedback or recovery actions.

  try {
+   final gameId = _reflectAndShareRepository.getGameId();
    await _gratefulRecommendationsRepository.savePledge(
      _getCurrentProfile(),
      organisation,
-     _reflectAndShareRepository.getGameId(),
+     gameId,
    );
+   // Optionally emit success state here
  } catch (e, s) {
    LoggingInfo.instance.logExceptionForDebug(e, stacktrace: s);
+   // Consider emitting specific error state for UI feedback
  }

Line range hint 1-270: Consider architectural improvements for better maintainability

The class handles multiple responsibilities and complex state management. Consider the following improvements:

  1. Add documentation for complex state transitions and game logic
  2. Consider splitting into smaller, focused cubits (e.g., separate profile management from recommendations)
  3. Add loading states for all async operations (currently only recommendations have loading state)
lib/features/family/features/giving_flow/screens/choose_amount_slider_screen.dart (1)

Line range hint 34-39: Consider separating game integration concerns.

The current implementation mixes game-related logic directly into the transaction creation flow. Consider:

  1. Creating a dedicated service class to handle game-transaction integration
  2. Moving the game ID logic to a separate mixin or helper
  3. Using a proper state management solution (e.g., BLoC) for game-related state instead of direct repository calls

This would improve:

  • Testability
  • Separation of concerns
  • Code reusability
  • Maintenance

Also applies to: 113-113

lib/features/family/app/injection.dart (3)

13-15: Fix file naming convention for consistency

The import path gratitude-summary/bloc/yesno-cubit.dart uses hyphens which is inconsistent with Dart file naming conventions. Dart files should use underscores for separators.

Rename the file to use underscores:

-import 'package:givt_app/features/family/features/gratitude-summary/bloc/yesno-cubit.dart';
+import 'package:givt_app/features/family/features/gratitude_summary/bloc/yesno_cubit.dart';

68-69: Consider making dependencies explicit in cubit registration

While the shorthand constructor syntax is clean, it hides the dependencies of these cubits. Consider making the dependencies explicit for better maintainability and consistency with other cubit registrations in this file.

-    ..registerFactory(ParentSummaryCubit.new)
-    ..registerFactory(YesNoCubit.new)
+    ..registerFactory(() => ParentSummaryCubit(getIt<ParentSummaryRepository>()))
+    ..registerFactory(() => YesNoCubit(getIt<ParentSummaryRepository>()))

151-155: Specify explicit dependencies for ParentSummaryRepository

The repository is registered using a raw getIt() call. For better maintainability and type safety, consider specifying the exact service or dependency that the repository requires.

    ..registerSingleton<ParentSummaryRepository>(
      ParentSummaryRepository(
-       getIt(),
+       getIt<FamilyAPIService>(), // or other specific service
      ),
    )
lib/features/family/shared/design/components/actions/fun_tile.dart (1)

47-47: Maintain consistent parameter ordering across factory methods

The ordering of the isPressedDown parameter is inconsistent between the gold and red factory methods. In the gold factory, it's passed after iconPath, while in the red factory, it's passed after onTap. Consider maintaining a consistent parameter order across all factory methods for better maintainability.

Suggested ordering for all factory methods:

  return FunTile(
    borderColor: ...,
    backgroundColor: ...,
    textColor: ...,
    iconPath: ...,
    onTap: ...,
    isPressedDown: isPressedDown,  // Consistent position after onTap
    titleBig: ...,
    // ... remaining parameters
  );

Also applies to: 100-100

lib/core/enums/amplitude_events.dart (2)

323-325: Fix typo in comment header

The comment header "Gratitude Summary" seems misplaced as it's followed by bedtime-related events. Consider moving it to a more appropriate location or updating it to "Bedtime Push Notifications".


326-329: Fix typo in comment header

There's a typo in the comment header "Bedikme summary". It should be "Bedtime summary".

lib/core/notification/notification_service.dart (2)

Line range hint 266-307: Consider reducing code duplication in summary notification handling

The ShowMonthlySummary and ShowYearlySummary cases follow identical patterns. Consider extracting this logic into a helper method.

Here's a suggested implementation:

+  Future<void> _handleSummaryNotification({
+    required Map<String, dynamic> customData,
+    required String type,
+  }) async {
+    LoggingInfo.instance.info('$type received');
+    await _showNotification(
+      message: customData['body'] as String,
+      title: customData['title'] as String,
+      payload: {'Type': type},
+    );
+  }

   switch (customData['Type']) {
     // ... other cases ...
     case 'ShowMonthlySummary':
-      LoggingInfo.instance.info('ShowMonthlySummary received');
-      await _showNotification(
-        message: customData['body'] as String,
-        title: customData['title'] as String,
-        payload: {'Type': 'ShowMonthlySummary'},
-      );
+      await _handleSummaryNotification(
+        customData: customData,
+        type: 'ShowMonthlySummary',
+      );
     case 'ShowYearlySummary':
-      LoggingInfo.instance.info('ShowYearlySummary received');
-      await _showNotification(
-        message: customData['body'] as String,
-        title: customData['title'] as String,
-        payload: {'Type': 'ShowYearlySummary'},
-      );
+      await _handleSummaryNotification(
+        customData: customData,
+        type: 'ShowYearlySummary',
+      );

Line range hint 449-467: Fix incorrect preference key usage in _shouldScheduleNotification

There are two critical issues in this method:

  1. The method always saves to monthlySummaryNotificationKey instead of using the provided prefKey
  2. The proposed schedule date is saved even when we're not scheduling the notification

Apply this fix:

   if (lastScheduledDateParsed.isBefore(proposedScheduleDate)) {
+    await prefs.setString(
+      prefKey,
+      proposedScheduleDate.toString(),
+    );
     return true;
   }
-  await prefs.setString(
-    monthlySummaryNotificationKey,
-    proposedScheduleDate.toString(),
-  );
   return false;
lib/features/family/app/family_routes.dart (1)

32-33: Consider renaming directory to follow Dart conventions

The import path contains a hyphen in 'gratitude-summary' which deviates from Dart's conventional snake_case naming pattern for directories. Consider renaming it to 'gratitude_summary' to maintain consistency with Dart conventions.

lib/features/family/features/reflect/domain/reflect_and_share_repository.dart (1)

88-91: Adjust logging parameters for accurate error reporting

Similar to previous instances, in the catch block, pass the actual method name and use the stack trace appropriately.

Apply this diff to fix:

      LoggingInfo.instance.error(
        e.toString(),
-       methodName: s.toString(),
+       methodName: 'saveGratitudeInterestsForCurrentSuperhero',
+       errorDetail: s.toString(),
      );
lib/features/family/network/family_api_service.dart (2)

276-283: Refactor repeated error handling logic into a helper method

The error handling code that checks the response.statusCode and throws GivtServerFailure is repeated across multiple methods. Refactoring this logic into a dedicated helper method will reduce code duplication and improve maintainability.

Here's how you can refactor the error handling:

Add a private method to handle the error response:

void _handleErrorResponse(Response response) {
  if (response.statusCode >= 300) {
    throw GivtServerFailure(
      statusCode: response.statusCode,
      body: response.body.isNotEmpty
          ? jsonDecode(response.body) as Map<String, dynamic>
          : null,
    );
  }
}

Then, replace the repeated error handling in your methods with a call to _handleErrorResponse(response);. For example:

 final response = await client.put(url,
     headers: {
       'Content-Type': 'application/json',
       'Accept': 'application/json',
     },
     body: jsonEncode(body));
- if (response.statusCode >= 300) {
-   throw GivtServerFailure(
-     statusCode: response.statusCode,
-     body: response.body.isNotEmpty
-         ? jsonDecode(response.body) as Map<String, dynamic>
-         : null,
-   );
- }
+ _handleErrorResponse(response);

Also applies to: 352-359, 370-376, 395-401, 408-414


383-393: Ensure consistent JSON body formatting

In the saveUserGratitudeCategory method, the formatting of the JSON body can be improved for better readability and consistency with the rest of the codebase.

Apply this diff to enhance readability:

   body: jsonEncode(
     {
-      'userid': userid, 
-      'category': category,}
+      'userid': userid,
+      'category': category,
     },
   ),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between deefec5 and 63fbcda.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (41)
  • .vscode/launch.json (1 hunks)
  • android/app/src/main/AndroidManifest.xml (1 hunks)
  • ios/Runner/Info.plist (1 hunks)
  • lib/core/enums/amplitude_events.dart (1 hunks)
  • lib/core/enums/country.dart (1 hunks)
  • lib/core/notification/notification_service.dart (1 hunks)
  • lib/features/family/app/family_pages.dart (1 hunks)
  • lib/features/family/app/family_routes.dart (3 hunks)
  • lib/features/family/app/injection.dart (4 hunks)
  • lib/features/family/features/giving_flow/create_transaction/models/transaction.dart (1 hunks)
  • lib/features/family/features/giving_flow/screens/choose_amount_slider_screen.dart (3 hunks)
  • lib/features/family/features/gratitude-summary/bloc/parent_summary_cubit.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/bloc/yesno-cubit.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/data/models/parent_summary_response.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/data/record_utils.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/domain/models/parent_summary_item.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/domain/repositories/parent_summary_repository.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/models/parent_summary_uimodel.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/pages/parent_summary_screen.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/pages/record_summary_message_.bottomsheet.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/pages/yesno_screen.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/widgets/audio_player.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/widgets/audio_recorder.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/widgets/summary_conversation_item.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/widgets/summary_conversation_list.dart (1 hunks)
  • lib/features/family/features/home_screen/presentation/pages/family_home_screen.dart (3 hunks)
  • lib/features/family/features/parent_giving_flow/cubit/give_cubit.dart (3 hunks)
  • lib/features/family/features/reflect/bloc/grateful_cubit.dart (1 hunks)
  • lib/features/family/features/reflect/bloc/summary_cubit.dart (2 hunks)
  • lib/features/family/features/reflect/domain/grateful_recommendations_repository.dart (1 hunks)
  • lib/features/family/features/reflect/domain/grateful_recommendations_repository_impl.dart (1 hunks)
  • lib/features/family/features/reflect/domain/models/summary_details.dart (1 hunks)
  • lib/features/family/features/reflect/domain/reflect_and_share_repository.dart (5 hunks)
  • lib/features/family/features/reflect/presentation/pages/record_answer_screen.dart (0 hunks)
  • lib/features/family/features/reflect/presentation/pages/summary_screen.dart (3 hunks)
  • lib/features/family/network/family_api_service.dart (5 hunks)
  • lib/features/family/shared/design/components/actions/fun_tile.dart (2 hunks)
  • lib/features/family/shared/design/components/navigation/fun_top_app_bar.dart (2 hunks)
  • lib/features/family/shared/design/illustrations/fun_icon.dart (1 hunks)
  • lib/features/family/utils/family_app_theme.dart (2 hunks)
  • pubspec.yaml (1 hunks)
💤 Files with no reviewable changes (1)
  • lib/features/family/features/reflect/presentation/pages/record_answer_screen.dart
🔇 Additional comments (51)
lib/features/family/features/gratitude-summary/presentation/models/parent_summary_uimodel.dart (1)

5-5: Verify audio link format and integration

The audioLink field should be compatible with the audio recording and playback functionality mentioned in the summary.

Also applies to: 9-9

✅ Verification successful

Audio link format and integration are properly implemented

The codebase shows a complete and well-integrated audio functionality:

  • The audioLink field in ParentSummaryUIModel is used with UrlSource in the audio player, supporting remote audio URLs
  • Audio recording is implemented using AudioRecorder with proper format (m4a) and configuration
  • Audio playback is handled by AudioPlayer widget with proper state management and controls
  • The audio file upload is properly implemented in FamilyApiService with correct MIME type and file handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify audio link format and integration with audio functionality

# Check audio link usage in audio player widget
rg -t dart "audioLink" lib/features/family/features/gratitude-summary/presentation/widgets/audio_player.dart

# Check audio recording format in recorder utils
rg -t dart "saveAudio|recordAudio" lib/features/family/features/gratitude-summary/data/record_utils.dart

Length of output: 215


Script:

#!/bin/bash
# Let's broaden the search to find audio-related functionality across the codebase

# Search for audio-related files
fd -e dart -e yaml audio

# Search for audio-related imports and implementations
rg -t dart "import.*audio|class.*Audio|audio" -A 3

# Search for audio player widget implementations
ast-grep --pattern 'class $_ extends StatefulWidget {
  $$$
  audio$_ $$$
  $$$
}'

Length of output: 30132

lib/features/family/features/reflect/domain/models/summary_details.dart (1)

1-1: LGTM: Import statement is correctly placed and necessary.

The import of Profile model is required for the new missingAdults field.

lib/features/family/features/reflect/domain/grateful_recommendations_repository.dart (1)

20-20: Consider specifying GUID validation requirements

The implementation class should validate the gameGuid format when provided. Consider adding a comment in the interface to specify the expected format and validation requirements.

Let's check if there's any GUID validation in the codebase:

lib/features/family/features/gratitude-summary/domain/repositories/parent_summary_repository.dart (1)

1-6: LGTM! Well-structured class with proper dependency injection.

The class follows good practices with constructor injection and proper encapsulation of the API service.

lib/features/family/features/gratitude-summary/presentation/widgets/summary_conversation_list.dart (2)

5-8: LGTM! Well-structured widget declaration

The class follows Flutter best practices with proper immutability, const constructor, and required parameter annotation.


1-23: Verify integration with ParentSummaryScreen

Let's verify that this widget is properly integrated with its parent screen and the audio player functionality mentioned in the summary.

✅ Verification successful

Widget properly integrated with parent screen and state management

The verification shows that:

  • SummaryConversationList is correctly integrated within ParentSummaryScreen
  • It's properly connected to state management via ParentSummaryCubit
  • The widget is used within a scrollable column layout with proper spacing
  • The conversations data is properly passed from the cubit's state via uiModel?.conversations
  • Null safety is handled with an empty list fallback
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of SummaryConversationList in ParentSummaryScreen
ast-grep --pattern 'class ParentSummaryScreen {
  $$$
  SummaryConversationList($$$)
  $$$
}'

# Check for proper state management integration
rg -l "ParentSummaryCubit" "lib/features/family/features/gratitude_summary"

Length of output: 256


Script:

#!/bin/bash
# Let's first find the correct path for gratitude-summary directory
fd -t d gratitude

# Search for usage of SummaryConversationList
rg "SummaryConversationList"

# Search for files containing ParentSummaryScreen
rg "class ParentSummaryScreen"

Length of output: 743


Script:

#!/bin/bash
# Let's check the implementation of ParentSummaryScreen to verify proper integration
cat "lib/features/family/features/gratitude-summary/presentation/pages/parent_summary_screen.dart"

# Let's also check the state management
rg "ParentSummaryCubit" lib/features/family/features/gratitude-summary/

Length of output: 4624

lib/features/family/features/gratitude-summary/bloc/yesno-cubit.dart (2)

10-13: LGTM!

Good use of dependency injection for repository initialization.


21-23: LGTM!

The click handler methods are simple, focused, and correctly implement their purpose.

Also applies to: 38-40

lib/features/family/features/reflect/bloc/summary_cubit.dart (1)

41-50: LGTM!

The method correctly updates the state with all necessary fields when audio becomes available.

lib/features/family/features/gratitude-summary/domain/models/parent_summary_item.dart (2)

4-9: LGTM! Well-structured class definition.

Good practices observed:

  • Immutable fields with final keyword
  • Appropriate use of nullable types for optional data
  • Sensible default value for conversations list

Also applies to: 25-27


38-62: 🛠️ Refactor suggestion

Add validation in Conversation.fromMap factory.

The current implementation assumes all required fields exist and are correctly typed, which could lead to runtime errors.

Consider this more robust implementation:

 factory Conversation.fromMap(Map<String, dynamic> map) {
+  if (!map.containsKey('sentence') || !map.containsKey('profile')) {
+    throw FormatException('Missing required fields: sentence and/or profile');
+  }
   final sentence = map['sentence'] as String;
+  if (sentence.trim().isEmpty) {
+    throw FormatException('Sentence cannot be empty');
+  }
+  final profileMap = map['profile'];
+  if (profileMap is! Map<String, dynamic>) {
+    throw FormatException('Invalid profile data format');
+  }
-  final profile = Profile.fromMap(map['profile'] as Map<String, dynamic>);
+  final profile = Profile.fromMap(profileMap);
   return Conversation(
     sentence: sentence,
     profile: profile,
   );
 }

Let's verify the Profile class implementation to ensure our assumptions about its fromMap method are correct:

✅ Verification successful

Add validation in Conversation.fromMap factory with Profile-aware implementation.

The Profile.fromMap implementation already handles null values and type casting, but we should still validate the required fields in Conversation.fromMap. Here's a refined suggestion:

 factory Conversation.fromMap(Map<String, dynamic> map) {
+  if (!map.containsKey('sentence') || !map.containsKey('profile')) {
+    throw FormatException('Missing required fields: sentence and/or profile');
+  }
   final sentence = map['sentence'] as String;
+  if (sentence.trim().isEmpty) {
+    throw FormatException('Sentence cannot be empty');
+  }
+  final profileMap = map['profile'];
+  if (profileMap is! Map<String, dynamic>) {
+    throw FormatException('Invalid profile data format');
+  }
   final profile = Profile.fromMap(profileMap);
   return Conversation(
     sentence: sentence,
     profile: profile,
   );
 }

The suggestion is appropriate because:

  1. Profile.fromMap already handles null values and type casting for its fields
  2. We only need to validate the structure and required fields at the Conversation level
  3. Any malformed profile data will be caught by Profile.fromMap's own type assertions
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Profile class implementation
ast-grep --pattern 'class Profile {
  $$$
  factory Profile.fromMap($_) {
    $$$
  }
  $$$
}'

Length of output: 96


Script:

#!/bin/bash
# Let's try a broader search for Profile class and its implementation
rg -U "class Profile[\s{]" -A 20

Length of output: 1863


Script:

#!/bin/bash
# Search for Profile.fromMap usage to find its implementation
rg "Profile\.fromMap"

Length of output: 830


Script:

#!/bin/bash
# Search for files containing Profile class
fd "profile\.dart"

Length of output: 274


Script:

#!/bin/bash
# Let's see the complete Profile.fromMap implementation
rg -U "factory Profile\.fromMap.*?^}" -A 30 --multiline lib/features/family/features/profiles/models/profile.dart

Length of output: 115


Script:

#!/bin/bash
# Let's try a simpler approach to get the Profile.fromMap implementation
cat lib/features/family/features/profiles/models/profile.dart

Length of output: 4927

lib/features/family/shared/design/components/navigation/fun_top_app_bar.dart (2)

12-12: LGTM: Constructor parameter addition follows best practices

The optional titleColor parameter is well-placed and maintains backward compatibility.


49-49: LGTM: Field declaration is properly implemented

The titleColor field is correctly declared as nullable and final, maintaining immutability.

lib/features/family/features/parent_giving_flow/cubit/give_cubit.dart (3)

9-9: LGTM: Import addition is appropriate

The new import for ReflectAndShareRepository is correctly placed and necessary for the new game-related functionality.


18-21: LGTM: Clean dependency injection implementation

The new repository is properly injected and stored as a final field, following good dependency injection practices and maintaining immutability.


36-38: 🛠️ Refactor suggestion

Verify error handling for game ID retrieval

While the conditional game ID assignment is logical, there are a few concerns to address:

  1. The getGameId() call lacks error handling
  2. We should verify if null game IDs are properly handled downstream

Let's verify the implementation of getGameId() and its error handling:

Consider wrapping the getGameId() call in a try-catch block:

      gameGuid: isGratitude
-          ? _reflectAndShareRepository.getGameId()
+          ? await _reflectAndShareRepository.getGameId().catchError((error) {
+              LoggingInfo.instance.error(
+                'Failed to get game ID: $error',
+                methodName: 'createTransaction',
+              );
+              return null;
+            })
          : null,
lib/features/family/features/gratitude-summary/presentation/widgets/summary_conversation_item.dart (1)

48-65: LGTM! Well-structured text layout

The text content implementation follows Flutter best practices:

  • Uses Flexible for proper text wrapping
  • Implements consistent spacing
  • Applies theme colors correctly
lib/features/family/app/family_pages.dart (1)

25-26: Verify route integration and screen implementations

Let's verify that these new routes are properly integrated with their corresponding screens and cubits.

✅ Verification successful

Let me gather more information about the YesNoScreen implementation since it wasn't found in the initial search.


Routes are properly integrated with their corresponding screens and cubits

The verification confirms proper integration:

  • Both routes are correctly defined in family_routes.dart
  • Screen implementations exist:
    • ParentSummaryScreen in gratitude-summary/presentation/pages/parent_summary_screen.dart
    • YesNoScreen in gratitude-summary/presentation/pages/yesno_screen.dart
  • Corresponding cubits are present:
    • parent_summary_cubit.dart
    • yesno-cubit.dart
  • Routes are being used (referenced in family_home_screen.dart)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the integration of new routes with screens and cubits

# Check for route definitions
echo "Checking route definitions..."
rg "FamilyPages\.(yesNo|parentSummary)" -A 3

# Check for screen implementations
echo "Checking screen implementations..."
fd -e dart "yes_no_screen|parent_summary_screen"

# Check for cubit implementations
echo "Checking cubit implementations..."
fd -e dart "(yes_no|parent_summary)_cubit"

Length of output: 1951


Script:

#!/bin/bash
# Search for YesNoScreen class definition
echo "Searching for YesNoScreen implementation..."
ast-grep --pattern 'class YesNoScreen extends $_'

# Search for any files containing YesNoScreen
echo "Searching files containing YesNoScreen..."
rg "YesNoScreen" -l

# Search for yes_no related files that might have different naming
echo "Searching for yes_no related files..."
fd "yes.*no"

Length of output: 641

.vscode/launch.json (1)

Line range hint 90-99: LGTM: Golden test configuration is well structured

The configuration correctly sets up golden test updates with appropriate codeLens integration for VS Code.

lib/core/enums/country.dart (1)

74-74: ⚠️ Potential issue

Changing fallback from unknown to nl could mask errors

This change silently treats invalid country codes as Netherlands, which could lead to incorrect:

  • Phone number formatting (wrong prefix +31)
  • Payment processing (wrong currency EUR)
  • Regional features/restrictions

Consider either:

  1. Maintaining error detection by keeping the unknown fallback, or
  2. Adding logging/monitoring for unmatched country codes if defaulting to Netherlands is intentional

Let's verify the usage of this method to understand the impact:

lib/features/family/features/gratitude-summary/presentation/pages/yesno_screen.dart (1)

104-115: Verify analytics events registration

The analytics events look properly implemented. Let's verify that these events are properly registered in the AmplitudeEvents enum.

✅ Verification successful

Based on the results, I can generate the final response as we have found clear evidence that both analytics events are properly registered in the AmplitudeEvents enum in lib/core/enums/amplitude_events.dart.

Analytics events are properly registered

Both whoDoesBedtimePushYesClicked and whoDoesBedtimePushNoClicked events are correctly defined in the AmplitudeEvents enum with their corresponding string values.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the analytics events are properly registered

# Check if the events are defined in the AmplitudeEvents enum
rg "whoDoesBedtimePushYesClicked|whoDoesBedtimePushNoClicked" -A 1 -B 1

Length of output: 1159

lib/features/family/features/gratitude-summary/presentation/pages/parent_summary_screen.dart (1)

1-22: LGTM! Clean imports and proper widget declaration.

The imports are well-organized and the StatefulWidget declaration follows Flutter conventions.

ios/Runner/Info.plist (2)

97-98: LGTM! Microphone permission properly implemented.

The microphone permission is correctly implemented with a clear and user-friendly description that explains why the permission is needed.


97-98: Verify integration with audio recording features.

Please ensure that the audio recording functionality properly handles cases where:

  1. The user denies microphone permission
  2. The user initially denies but later grants permission via Settings
✅ Verification successful

Let me search for audio recording related code and error handling to verify the microphone permission handling.


Based on the code analysis, I can now provide a final response:

Audio recording permission handling is properly implemented

The codebase shows proper handling of microphone permissions through:

  • Permission check before recording via _audioRecorder.hasPermission()
  • Try-catch block around recording operations to handle errors
  • Clear user messaging in Info.plist for iOS permission request
  • Proper cleanup in the dispose method

Key implementation in audio_recorder.dart:

  • Permission verification before starting recording
  • Error handling for unsupported encoders
  • State management for recording lifecycle
  • Proper resource cleanup
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify proper error handling for microphone permission

# Search for permission handling code
rg -A 5 "requestRecorder|Permission|permission.*microphone" 

# Look for error handling patterns
ast-grep --pattern 'try {
  $$$
} catch ($_, $_) {
  $$$
}'

Length of output: 27170


Script:

#!/bin/bash
# Search for audio recording related code and error handling
rg -A 10 "audioRecorder|record.*audio|audio.*record" 

# Search for microphone permission handling
rg -A 10 "microphone.*permission|permission.*microphone|Permission\.microphone"

Length of output: 15239

pubspec.yaml (1)

82-83: Verify security and stability of new audio dependencies

Both packages use caret versioning which could lead to compatibility issues. Let's verify:

  1. The stability of these versions
  2. Any known security vulnerabilities
  3. Compatibility with existing dependencies

Run the following checks:

Consider pinning these versions to specific releases instead of using caret versions to ensure stability:

-  record: ^5.2.0
-  audioplayers: ^6.1.0
+  record: 5.2.0
+  audioplayers: 6.1.0
✅ Verification successful

Let me gather more information about the package usage and potential conflicts.

This will help us understand:

  1. The exact versions being used (from lock file)
  2. How these packages are imported and used
  3. Any error handling that might indicate version-specific issues

Let me check the actual implementation to see if there are any version-specific features being used.


Dependencies are safe to use with caret versioning

Based on the verification:

  1. No security vulnerabilities found for either package
  2. Implementation uses standard APIs (play, pause, stop, seek) from both packages
  3. Proper error handling and state management is in place
  4. Usage patterns align with the latest stable versions

The suggestion to pin versions isn't necessary as:

  • Both packages follow semantic versioning
  • The implementation doesn't rely on version-specific features
  • The codebase properly handles audio states and cleanup
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check package information and security

# Check for any known vulnerabilities
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: PUB, package: "record") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
    }
  }
}
'

# Check for any known vulnerabilities in audioplayers
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: PUB, package: "audioplayers") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
    }
  }
}
'

# Look for potential version conflicts
echo "Checking for version conflicts..."
rg "record:|audioplayers:" 

Length of output: 860


Script:

#!/bin/bash
# Check package versions in lock file and usage patterns
echo "Checking locked versions..."
rg "^(record|audioplayers):" -A 2 pubspec.lock

echo "Checking import and usage patterns..."
rg "import.*package:(record|audioplayers)" 

echo "Checking for any error handling or version-specific code..."
ast-grep --pattern 'try { $$$ } catch' 

Length of output: 1063


Script:

#!/bin/bash
# Check implementation details
echo "Checking record implementation..."
rg -A 10 "class.*Record" lib/features/family/features/gratitude-summary/

echo "Checking audioplayer implementation..."
rg -A 10 "class.*Player" lib/features/family/features/gratitude-summary/

echo "Checking for version-specific method calls..."
rg "audioPlayer\.|record\." lib/features/family/features/gratitude-summary/

Length of output: 10263

lib/features/family/features/gratitude-summary/bloc/parent_summary_cubit.dart (2)

1-18: LGTM! Clean class structure and proper dependency management.

The class is well-structured with appropriate type parameters and follows dependency injection best practices.


39-67: ⚠️ Potential issue

Review security implications of hardcoded profile data.

The code contains potentially sensitive information:

  1. Profile IDs (e.g., "ea04efcc-f9c4-433d-90c3-8df1d14a9625")
  2. Debug storage URLs (givtstoragedebug.blob.core.windows.net)

Consider:

  • Using environment-specific configuration for URLs
  • Using mock IDs for development/testing
  • Implementing proper data masking for sensitive fields

Also applies to: 71-99

lib/features/family/shared/design/illustrations/fun_icon.dart (1)

64-74: LGTM! Well-implemented recording icon

The implementation follows the established pattern and uses appropriate error-themed colors for recording indication. This aligns well with the audio recording functionality mentioned in the summary.

lib/features/family/features/reflect/domain/grateful_recommendations_repository_impl.dart (2)

145-145: LGTM! Method signature change looks good.

The addition of the optional gameGuid parameter is well-implemented with proper null safety.


151-151: Verify the API contract for the 'GameId' field.

Before implementing the suggested naming changes, please verify that the API specifically expects 'GameId' as the field name. If this is a requirement from the backend, we should maintain this format despite it not following common conventions.

Also applies to: 156-156

✅ Verification successful

The 'GameId' field name is consistently used across the codebase and API contract

Based on the evidence from the codebase:

  • The Transaction model in create_transaction/models/transaction.dart uses 'GameId' in its JSON serialization
  • The savePledge method in grateful_recommendations_repository_impl.dart uses the same 'GameId' field
  • The API service implementation shows this field is used in multiple game-related endpoints

The current field name 'GameId' is part of the established API contract and should be maintained to ensure compatibility.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other occurrences of 'GameId' in API-related files
rg -l "GameId" --type dart | grep -i "api\|service\|network"

Length of output: 60


Script:

#!/bin/bash
# Let's try a broader search for 'GameId' in all Dart files and include context
rg -A 5 -B 5 "GameId" --type dart

Length of output: 7504


Script:

#!/bin/bash
# Let's also check the file content to understand the context better
cat lib/features/family/features/reflect/domain/grateful_recommendations_repository_impl.dart

Length of output: 5175


Script:

#!/bin/bash
# Search for any API/network related files that might define the contract
fd -e dart -e json . | grep -i "api\|service\|network\|model"

Length of output: 7029


Script:

#!/bin/bash
# Let's check the API service implementation to see the contract
cat lib/features/family/network/family_api_service.dart

Length of output: 13798


Script:

#!/bin/bash
# Let's also check the Transaction model implementation
cat lib/features/family/features/giving_flow/create_transaction/models/transaction.dart

Length of output: 685

lib/features/family/features/reflect/presentation/pages/summary_screen.dart (1)

42-45: LGTM! Clear state management for audio UI visibility

The boolean flags create mutually exclusive states for the audio recorder and player visibility, which is the correct approach.

lib/features/family/features/gratitude-summary/presentation/widgets/audio_recorder.dart (2)

1-18: LGTM! Clean class structure and proper dependency management.

The class is well-structured with appropriate imports and a clear callback interface.


1-210: Verify audio recording functionality across platforms.

The implementation looks solid, but we should verify the audio recording functionality works correctly across different platforms and devices.

✅ Verification successful

Audio recording permissions and platform configurations are properly set up

The verification confirms that:

  • Android manifest includes required permissions: RECORD_AUDIO, MODIFY_AUDIO_SETTINGS
  • iOS Info.plist contains NSMicrophoneUsageDescription with proper user message
  • The audio recorder implementation uses the record package which handles platform-specific implementations internally
  • No platform-specific code is needed in the audio recorder widget itself, making it work consistently across platforms
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for platform-specific configurations and permissions

# Check Android manifest for required permissions
echo "Checking Android permissions..."
rg -A 2 "android.permission.RECORD_AUDIO" "android/app/src/main/AndroidManifest.xml"

# Check iOS permissions
echo "Checking iOS permissions..."
rg -A 2 "NSMicrophoneUsageDescription" "ios/Runner/Info.plist"

# Check for platform-specific code
echo "Checking platform-specific implementations..."
rg "Platform\." "lib/"

Length of output: 5227

lib/features/family/features/home_screen/presentation/pages/family_home_screen.dart (3)

23-23: LGTM: Analytics import is properly integrated

The new analytics import is correctly placed and appropriately used for tracking button interactions.


125-133: LGTM: Button implementation follows UI patterns

The new summary button is well-integrated:

  • Proper spacing with SizedBox
  • Uses consistent FunButton widget
  • Clear, descriptive text
  • Secondary style appropriately indicates it's not the primary action

127-129: Verify the parentSummary route configuration

The button navigates to the parentSummary route. Let's verify this route is properly configured.

✅ Verification successful

Let me gather more specific information about the parentSummary route configuration.


Route configuration for parentSummary is properly set up

The route is correctly configured:

  • Defined in FamilyPages enum as parentSummary with path 'parent-summary'
  • Properly registered in family_routes.dart as a GoRoute with matching path and name
  • Navigates to ParentSummaryScreen
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the parentSummary route is defined in FamilyPages and properly configured in routes

# Search for parentSummary route definition
echo "Searching for parentSummary route definition..."
rg -l "parentSummary" --type dart

# Search for route handler implementation
echo "Searching for route handler implementation..."
ast-grep --pattern 'GoRoute(
  $$$
  name: $route_name,
  path: $path,
  $$$
)'

Length of output: 490


Script:

#!/bin/bash
# Check the specific route configuration in the files found

# Check FamilyPages enum definition
echo "Checking FamilyPages enum definition:"
rg "parentSummary" lib/features/family/app/family_pages.dart -C 2

# Check route configuration in family_routes.dart
echo -e "\nChecking route configuration in family_routes.dart:"
rg "parentSummary" lib/features/family/app/family_routes.dart -C 3

Length of output: 770

lib/features/family/features/reflect/bloc/grateful_cubit.dart (1)

167-167: Verify error handling for getGameId()

While adding the game ID is aligned with the game summary feature, ensure that getGameId() calls are properly handled in case of null or error scenarios.

Let's verify the implementation of getGameId():

✅ Verification successful

Let me analyze the results and generate the final response.

Null handling is already in place for getGameId()

The implementation and usage of getGameId() is properly handled:

  • ReflectAndShareRepository.getGameId() returns a nullable String (String?)
  • All usages of getGameId() are in contexts where null is acceptable:
    • In give_cubit.dart and choose_amount_slider_screen.dart, it's used with a ternary operator where null is an explicit alternative
    • In grateful_cubit.dart, it's used in a try-catch block that handles any potential errors
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for getGameId implementation and its error handling
ast-grep --pattern 'getGameId() {
  $$$
}'

# Check if there are any null checks when using getGameId
rg 'getGameId\(\)' -A 3 -B 3

Length of output: 3051

android/app/src/main/AndroidManifest.xml (1)

42-43: LGTM: Audio permissions are appropriate for the feature.

The RECORD_AUDIO and MODIFY_AUDIO_SETTINGS permissions align well with the audio recording functionality being added for the game summary feature.

lib/features/family/features/giving_flow/screens/choose_amount_slider_screen.dart (2)

8-8: LGTM: New imports are properly organized and necessary.

The added imports support the new game-related functionality and follow Flutter's import conventions.

Also applies to: 18-18


113-113: 🛠️ Refactor suggestion

Add error handling for getGameId() call.

The direct call to getIt<ReflectAndShareRepository>().getGameId() within the Transaction constructor could lead to runtime issues if:

  1. The game ID is not available
  2. The repository is not properly initialized
  3. The repository throws an exception

Consider extracting this logic and adding proper error handling:

- gameGuid: isActOfService ? getIt<ReflectAndShareRepository>().getGameId() : null,
+ gameGuid: isActOfService ? _getGameGuid(context) : null,

+ String? _getGameGuid(BuildContext context) {
+   try {
+     final gameId = getIt<ReflectAndShareRepository>().getGameId();
+     if (gameId == null || gameId.isEmpty) {
+       log('Warning: Game ID is null or empty for act of service');
+     }
+     return gameId;
+   } catch (e) {
+     log('Error getting game ID: $e');
+     SnackBarHelper.showMessage(
+       context,
+       text: 'Unable to link to game. Proceeding without game context.',
+       isError: true,
+     );
+     return null;
+   }
+ }

Let's verify the repository's initialization and usage:

lib/features/family/app/injection.dart (1)

Line range hint 13-155: Verify dependency resolution order

Let's verify there are no circular dependencies and that the initialization order is correct.

✅ Verification successful

Based on the shell script results, I can now generate the final response:

Dependency initialization is properly structured

The initialization order in injection.dart follows a correct pattern:

  • Repositories are initialized first via initRepositories()
  • Cubits are initialized after via initCubits()
  • ParentSummaryRepository is correctly registered before its dependent cubits
  • No circular dependencies found between ParentSummaryCubit, YesNoCubit and their dependencies
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential circular dependencies in the gratitude summary feature

# Search for references to ParentSummaryCubit and YesNoCubit
echo "Checking ParentSummaryCubit usage:"
rg "ParentSummaryCubit" --type dart

echo -e "\nChecking YesNoCubit usage:"
rg "YesNoCubit" --type dart

echo -e "\nChecking ParentSummaryRepository usage:"
rg "ParentSummaryRepository" --type dart

# Check if these components are used in any initialization code
echo -e "\nChecking initialization order:"
rg "initState|initBloc|init\(" --type dart

Length of output: 20347

lib/features/family/shared/design/components/actions/fun_tile.dart (1)

40-40: Verify the impact on existing FunTile usages

Since isPressedDown has been added to the factory constructors, we should verify that existing usages of FunTile are not affected and that the audio feedback (if any) is properly integrated with the pressed state.

Also applies to: 92-92

✅ Verification successful

Based on the search results, I can generate the final response as I have enough context about the isPressedDown usage and audio-related interactions:

No impact on existing FunTile usages

The isPressedDown parameter is a new optional parameter with a default value of false, making it backward compatible. The search results show that existing FunTile usages in summary_screen.dart don't need modification. Additionally, the audio-related code is properly encapsulated in dedicated audio components (audio_player.dart and audio_recorder.dart) and doesn't directly interact with FunTile's pressed state.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for existing FunTile usages to ensure they're compatible with the new parameter
rg "FunTile\.(gold|red|blue)\(" -A 5

# Look for any audio-related code that might interact with FunTile's pressed state
rg "audioplayers|record" --type dart -A 5

Length of output: 29465

lib/features/family/utils/family_app_theme.dart (2)

58-58: LGTM: highlight50 color addition

The new highlight50 color constant follows the established naming convention and fits well within the existing highlight color palette hierarchy.


76-77: Verify usage of new neutral colors

The new neutral70 and neutral90 color constants are well-positioned and follow the established naming convention. Let's verify their intended usage in the new UI components.

✅ Verification successful

The new neutral colors are used consistently in the audio player component

The verification shows that both neutral70 and neutral90 colors are being used appropriately in the audio player component within the gratitude summary feature:

  • neutral70 is used for the delete button color
  • neutral90 is used as the inactive color for a component

The colors are well-defined in the theme file and used in a consistent manner within the UI components they were intended for.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check where the new neutral colors are being used in the UI components

# Search for neutral90 color usage
echo "Checking neutral90 color usage:"
rg -l "neutral90" --type dart

# Search for neutral70 color usage
echo "Checking neutral70 color usage:"
rg -l "neutral70" --type dart

# Search specifically in new gratitude summary related files
echo "Checking in gratitude summary related files:"
fd -e dart | grep -i "summary" | xargs rg "neutral(70|90)"

Length of output: 930

lib/core/enums/amplitude_events.dart (1)

324-332: LGTM! Event declarations follow consistent patterns

The new amplitude events follow the established naming conventions and value patterns:

  • Clear, descriptive names that indicate the user action
  • Values in snake_case format
  • Proper grouping of related events
lib/core/notification/notification_service.dart (1)

Line range hint 1-516: Overall implementation looks solid

The notification service implementation is well-structured with:

  • Comprehensive logging for debugging
  • Good error handling for device-specific cases
  • Clear separation of concerns between different notification types
  • Proper scheduling logic for notifications

The identified issues have been addressed in the previous comments.

lib/features/family/app/family_routes.dart (1)

148-152: LGTM: ParentSummaryScreen route configuration

The route configuration for ParentSummaryScreen is simple and follows the established pattern in the codebase.

lib/features/family/features/reflect/domain/reflect_and_share_repository.dart (3)

1-1: Approved: Importing 'dart:io' library is necessary

The import of 'dart:io' is required for file operations in the shareAudio method.


140-150: Approved: The missingAdults method logic is correct

The method correctly identifies adult profiles who are not in the selected profiles list.


159-159: ⚠️ Potential issue

Await createGameSession() to ensure game session is initialized

Since createGameSession() is an asynchronous method, you should await its completion to ensure that the game session is properly initialized before proceeding.

Apply this diff to fix:

-    createGameSession();
+    await createGameSession();

This change requires updating selectProfiles to be an async method:

-  void selectProfiles(List<GameProfile> selectedProfiles) {
+  Future<void> selectProfiles(List<GameProfile> selectedProfiles) async {

Ensure that any callers of selectProfiles handle the Future accordingly.

Please verify that making selectProfiles asynchronous does not introduce any unintended side effects in the codebase.

lib/features/family/network/family_api_service.dart (1)

448-461: Method putKidToBed appears correct

The putKidToBed method is well-implemented, using named required parameters and delegating the request to _postRequest. Error handling aligns with the existing patterns in the codebase.

Comment on lines 8 to 18
Future<bool> sendYesNoPutKidToBed({
required String childGuid,
required String parentGuid,
required bool yes,
}) async {
return _familyAPIService.putKidToBed(
childGuid: childGuid,
parentGuid: parentGuid,
yes: yes,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider improving parameter naming and error handling.

  1. The yes parameter name is not descriptive enough. Consider renaming it to something more meaningful like wasSuccessful or didPutToBed.
  2. The method lacks error handling for API failures.

Consider applying this improvement:

 Future<bool> sendYesNoPutKidToBed({
   required String childGuid,
   required String parentGuid,
-  required bool yes,
+  required bool didPutToBed,
 }) async {
-  return _familyAPIService.putKidToBed(
-    childGuid: childGuid,
-    parentGuid: parentGuid,
-    yes: yes,
-  );
+  try {
+    return await _familyAPIService.putKidToBed(
+      childGuid: childGuid,
+      parentGuid: parentGuid,
+      yes: didPutToBed,
+    );
+  } catch (e) {
+    // Log the error or handle it appropriately
+    throw Exception('Failed to update bed time status: $e');
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<bool> sendYesNoPutKidToBed({
required String childGuid,
required String parentGuid,
required bool yes,
}) async {
return _familyAPIService.putKidToBed(
childGuid: childGuid,
parentGuid: parentGuid,
yes: yes,
);
}
Future<bool> sendYesNoPutKidToBed({
required String childGuid,
required String parentGuid,
required bool didPutToBed,
}) async {
try {
return await _familyAPIService.putKidToBed(
childGuid: childGuid,
parentGuid: parentGuid,
yes: didPutToBed,
);
} catch (e) {
// Log the error or handle it appropriately
throw Exception('Failed to update bed time status: $e');
}
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (5)
lib/features/family/features/reflect/presentation/pages/summary_screen.dart (1)

61-66: Review the isPressedDown property usage

The isPressedDown property is set to true for both tiles but appears to be static. Consider:

  1. If this is intended to be interactive, it should be tied to a state variable
  2. If it's meant to be static, consider moving it to a constant or removing it if unnecessary

Also applies to: 76-81

lib/features/family/network/family_api_service.dart (3)

363-383: Consider making the empty request body more explicit

While the implementation is correct, the empty JSON body could be made more explicit to indicate that it's intentional.

Apply this diff to improve clarity:

-        body: jsonEncode({}));
+        body: jsonEncode({
+          // Intentionally empty - no initial game state required
+        }));

385-406: Add input validation for gratitude category

The method should validate the category parameter before making the API call to prevent invalid data from being sent to the server.

Apply this diff to add validation:

   Future<bool> saveUserGratitudeCategory(
       String gameGuid, String userid, String category) async {
+    if (category.trim().isEmpty) {
+      throw ArgumentError('Category cannot be empty');
+    }
     final url = Uri.https(_apiURL, '/givtservice/v1/game/$gameGuid/user');

408-422: Handle potential null response more gracefully

The method uses force unwrap (!) on the response item which could throw at runtime if the server returns null.

Apply this diff to handle null responses more gracefully:

     final decodedBody = jsonDecode(response.body) as Map<String, dynamic>;
-    final itemMap = decodedBody['item']! as Map<String, dynamic>;
-    return itemMap;
+    final item = decodedBody['item'];
+    if (item == null) {
+      throw GivtServerFailure(
+        statusCode: response.statusCode,
+        body: {'error': 'Server returned null item'},
+      );
+    }
+    return item as Map<String, dynamic>;
lib/features/family/features/reflect/domain/reflect_and_share_repository.dart (1)

161-171: Optimize missing adults calculation using Set operations.

The current implementation using multiple where clauses could be optimized for better performance.

Consider this more efficient implementation:

  Future<List<Profile>> missingAdults() async {
    final profiles = await _profilesRepository.getProfiles();
-   final missingAdults = profiles
-       .where((profile) => profile.isAdult)
-       .where((profile) => !_selectedProfiles
-           .map((selectedProfile) => selectedProfile.userId)
-           .contains(profile.id))
-       .toList();
+   final selectedIds = _selectedProfiles
+       .map((selectedProfile) => selectedProfile.userId)
+       .toSet();
+   final missingAdults = profiles
+       .where((profile) => profile.isAdult && !selectedIds.contains(profile.id))
+       .toList();
    return missingAdults;
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 63fbcda and 44e89e6.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • lib/core/enums/amplitude_events.dart (1 hunks)
  • lib/features/family/app/family_routes.dart (2 hunks)
  • lib/features/family/app/injection.dart (4 hunks)
  • lib/features/family/features/home_screen/presentation/pages/family_home_screen.dart (3 hunks)
  • lib/features/family/features/reflect/domain/reflect_and_share_repository.dart (6 hunks)
  • lib/features/family/features/reflect/presentation/pages/summary_screen.dart (3 hunks)
  • lib/features/family/network/family_api_service.dart (5 hunks)
  • lib/features/family/shared/design/components/navigation/fun_top_app_bar.dart (2 hunks)
  • lib/features/family/utils/family_app_theme.dart (2 hunks)
  • pubspec.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • lib/core/enums/amplitude_events.dart
  • lib/features/family/app/family_routes.dart
  • lib/features/family/app/injection.dart
  • lib/features/family/features/home_screen/presentation/pages/family_home_screen.dart
  • lib/features/family/shared/design/components/navigation/fun_top_app_bar.dart
  • lib/features/family/utils/family_app_theme.dart
  • pubspec.yaml
🧰 Additional context used
📓 Learnings (1)
lib/features/family/features/reflect/domain/reflect_and_share_repository.dart (1)
Learnt from: Daniela510
PR: givtnl/givt-app#1125
File: lib/features/family/app/injection.dart:160-162
Timestamp: 2024-11-12T03:51:15.016Z
Learning: The `ReflectAndShareRepository` constructor requires two parameters, and the dependency injection provides two instances accordingly.
🔇 Additional comments (9)
lib/features/family/features/reflect/presentation/pages/summary_screen.dart (4)

8-9: LGTM: Imports are correctly placed

The new imports for audio recording and playback functionality are appropriately added.


108-109: Implement the onDelete callback for AudioPlayer

The onDelete callback is still empty (() {}), which might lead to resource leaks if audio files aren't properly cleaned up.


113-115: Enhance audio sharing implementation

The current implementation still lacks proper error handling and loading states for the sharing functionality.


43-46: Verify the recorder visibility condition

The condition for showing the recorder (!details.allAdultsPlayed && details.audioPath.isEmpty) implies that:

  1. Not all adults have played yet
  2. No audio message exists

Please verify if this is the intended behavior. Should users be able to record a message even after all adults have played?

✅ Verification successful

Recorder visibility logic is correctly implemented

The condition for showing the recorder is logically sound based on the codebase evidence:

  • allAdultsPlayed is a computed property that returns true when missingAdults list is empty, meaning all adults have completed their turns
  • The recorder is shown only when there's no existing audio (audioPath.isEmpty) AND there are still adults who haven't played (!allAdultsPlayed)
  • This ensures that audio messages can only be recorded while the game is still in progress (before all adults complete their turns)
  • Once all adults have played, the recorder is hidden, preventing additional messages from being recorded after the game is complete
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for related business logic that might define this behavior
ast-grep --pattern 'allAdultsPlayed' 

Length of output: 36


Script:

#!/bin/bash
# Search for files containing 'allAdultsPlayed'
rg -l "allAdultsPlayed"

# Search for the context where allAdultsPlayed is used
rg "allAdultsPlayed" -B 2 -A 2

# Look for any model or state class that might contain this property
ast-grep --pattern 'class $_ {
  $$$
  bool allAdultsPlayed
  $$$
}'

Length of output: 1324


Script:

#!/bin/bash
# Let's look at the complete SummaryDetails class to understand the model better
rg -A 20 "class SummaryDetails" lib/features/family/features/reflect/domain/models/summary_details.dart

# Also check for any tests that might verify this behavior
fd "summary.*test" 

Length of output: 556

lib/features/family/network/family_api_service.dart (3)

344-361: LGTM! Well-structured error handling

The implementation includes proper error handling and follows the established patterns in the codebase.


467-480: LGTM! Clean implementation using existing patterns

The method is well-structured with required parameters and properly utilizes the existing _postRequest helper.


321-322: ⚠️ Potential issue

Avoid using null check operator on a possibly null gameGuid

The parameter gameGuid is declared as nullable (String? gameGuid) but is immediately used with the null assertion operator !, which will throw a runtime exception if gameGuid is null.

Apply this diff to fix the issue:

-  Future<bool> saveGratitudeStats(int duration, String? gameGuid) async {
+  Future<bool> saveGratitudeStats({
+    required int duration,
+    required String gameGuid,
+  }) async {
-    return updateGame(gameGuid!, {
+    return updateGame(gameGuid, {
       'type': 'Gratitude',
       'duration': duration,
     });
lib/features/family/features/reflect/domain/reflect_and_share_repository.dart (2)

16-17: Verify constructor parameter count against dependency injection.

The constructor now accepts three parameters (_profilesRepository, _familyApiService, _authRepository), but according to the retrieved learnings, it should only require two parameters. This might cause dependency injection issues.


84-96: ⚠️ Potential issue

Multiple critical issues in audio sharing implementation.

  1. Force unwrapping _gameId without null check could cause runtime crashes
  2. File is deleted regardless of upload success
  3. Basic exception handling with print(e) instead of proper logging
  4. Unaddressed TODO comments

Apply this diff to fix the issues:

  Future<void> shareAudio(String path) async {
    try {
      final file = File(path);
      if (file.existsSync()) {
-       //TODO use game guid
-       _familyApiService.uploadAudioFile(_gameId!, file);
-       file.delete();
+       if (_gameId == null) {
+         throw StateError('Game session not initialized');
+       }
+       await _familyApiService.uploadAudioFile(_gameId!, file);
+       await file.delete();
      }
    } on Exception catch (e, s) {
-     print(e);
-     // TODO what if fails
+     LoggingInfo.instance.error(
+       'Failed to share audio',
+       methodName: 'shareAudio',
+       errorDetail: s.toString(),
+     );
+     rethrow;  // Allow caller to handle the error
    }
  }

Likely invalid or redundant comment.

Comment on lines +259 to +296
Future<bool> uploadAudioFile(String gameGuid, File audioFile) async {
final url =
Uri.https(_apiURL, '/givtservice/v1/game/$gameGuid/upload-message');

final request = MultipartRequest('POST', url)
..headers['Content-Type'] = 'multipart/form-data'
..files.add(
MultipartFile(
'audio', // Name of the field expected by the server
audioFile.readAsBytes().asStream(),
audioFile.lengthSync(),
filename: 'audio_summary_message.m4a',
),
);

final response = await Response.fromStream(await request.send());

if (response.statusCode >= 300) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: response.body.isNotEmpty
? jsonDecode(response.body) as Map<String, dynamic>
: null,
);
}

final decodedBody = jsonDecode(response.body) as Map<String, dynamic>;
final isError = decodedBody['isError'] as bool;

if (response.statusCode == 200 && isError) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: decodedBody,
);
}

return response.statusCode == 200;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add file validation and make filename configurable

While the upload functionality is implemented correctly, there are several improvements needed:

  1. The filename is hardcoded which limits flexibility
  2. Missing file size and type validation before upload
  3. Manual Content-Type header setting (as previously noted)

Apply this diff to improve the implementation:

-  Future<bool> uploadAudioFile(String gameGuid, File audioFile) async {
+  Future<bool> uploadAudioFile(
+    String gameGuid,
+    File audioFile, {
+    String filename = 'audio_summary_message.m4a',
+    int maxSizeBytes = 10 * 1024 * 1024, // 10MB limit
+  }) async {
+    if (!audioFile.existsSync()) {
+      throw ArgumentError('Audio file does not exist');
+    }
+    
+    if (audioFile.lengthSync() > maxSizeBytes) {
+      throw ArgumentError('File size exceeds maximum allowed size');
+    }
+    
+    final extension = filename.split('.').last.toLowerCase();
+    if (!['m4a', 'mp3', 'wav'].contains(extension)) {
+      throw ArgumentError('Unsupported audio file format');
+    }

     final url =
         Uri.https(_apiURL, '/givtservice/v1/game/$gameGuid/upload-message');

     final request = MultipartRequest('POST', url)
-      ..headers['Content-Type'] = 'multipart/form-data'
       ..files.add(
         MultipartFile(
           'audio',
           audioFile.readAsBytes().asStream(),
           audioFile.lengthSync(),
-          filename: 'audio_summary_message.m4a',
+          filename: filename,
         ),
       );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<bool> uploadAudioFile(String gameGuid, File audioFile) async {
final url =
Uri.https(_apiURL, '/givtservice/v1/game/$gameGuid/upload-message');
final request = MultipartRequest('POST', url)
..headers['Content-Type'] = 'multipart/form-data'
..files.add(
MultipartFile(
'audio', // Name of the field expected by the server
audioFile.readAsBytes().asStream(),
audioFile.lengthSync(),
filename: 'audio_summary_message.m4a',
),
);
final response = await Response.fromStream(await request.send());
if (response.statusCode >= 300) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: response.body.isNotEmpty
? jsonDecode(response.body) as Map<String, dynamic>
: null,
);
}
final decodedBody = jsonDecode(response.body) as Map<String, dynamic>;
final isError = decodedBody['isError'] as bool;
if (response.statusCode == 200 && isError) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: decodedBody,
);
}
return response.statusCode == 200;
}
Future<bool> uploadAudioFile(
String gameGuid,
File audioFile, {
String filename = 'audio_summary_message.m4a',
int maxSizeBytes = 10 * 1024 * 1024, // 10MB limit
}) async {
if (!audioFile.existsSync()) {
throw ArgumentError('Audio file does not exist');
}
if (audioFile.lengthSync() > maxSizeBytes) {
throw ArgumentError('File size exceeds maximum allowed size');
}
final extension = filename.split('.').last.toLowerCase();
if (!['m4a', 'mp3', 'wav'].contains(extension)) {
throw ArgumentError('Unsupported audio file format');
}
final url =
Uri.https(_apiURL, '/givtservice/v1/game/$gameGuid/upload-message');
final request = MultipartRequest('POST', url)
..files.add(
MultipartFile(
'audio',
audioFile.readAsBytes().asStream(),
audioFile.lengthSync(),
filename: filename,
),
);
final response = await Response.fromStream(await request.send());
if (response.statusCode >= 300) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: response.body.isNotEmpty
? jsonDecode(response.body) as Map<String, dynamic>
: null,
);
}
final decodedBody = jsonDecode(response.body) as Map<String, dynamic>;
final isError = decodedBody['isError'] as bool;
if (response.statusCode == 200 && isError) {
throw GivtServerFailure(
statusCode: response.statusCode,
body: decodedBody,
);
}
return response.statusCode == 200;
}

Comment on lines +179 to +189
Future<void> createGameSession() async {
try {
_gameId = await _familyApiService.createGame();
} catch (e, s) {
_gameId = null;
LoggingInfo.instance.error(
e.toString(),
methodName: s.toString(),
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix async/await usage in game session initialization.

  1. createGameSession silently fails by setting _gameId to null
  2. selectProfiles calls createGameSession without awaiting the result

Apply these changes:

  Future<void> createGameSession() async {
    try {
      _gameId = await _familyApiService.createGame();
    } catch (e, s) {
      _gameId = null;
      LoggingInfo.instance.error(
        e.toString(),
        methodName: s.toString(),
      );
+     rethrow;  // Notify callers of failure
    }
  }

- void selectProfiles(List<GameProfile> selectedProfiles) {
+ Future<void> selectProfiles(List<GameProfile> selectedProfiles) async {
    // Reset game state
-   createGameSession();
+   await createGameSession();
    completedLoops = 0;

Also applies to: 193-194

Comment on lines +73 to +74
await _familyApiService.saveGratitudeStats(
totalTimeSpentInSeconds, _gameId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null safety checks for time variables and game ID.

Both methods force unwrap nullable variables which could cause runtime crashes:

  1. saveSummaryStats: _startTime! and _endTime!
  2. saveGratitudeInterestsForCurrentSuperhero: _gameId!

Apply these changes:

  Future<void> saveSummaryStats() async {
    try {
      _endTime = DateTime.now();
+     if (_startTime == null) {
+       throw StateError('Start time not initialized');
+     }
      totalTimeSpentInSeconds = _endTime!.difference(_startTime!).inSeconds;
+     if (_gameId == null) {
+       throw StateError('Game session not initialized');
+     }
      await _familyApiService.saveGratitudeStats(
          totalTimeSpentInSeconds, _gameId);
  Future<void> saveGratitudeInterestsForCurrentSuperhero(
      GratitudeCategory? gratitude) async {
+   if (_gameId == null) {
+     throw StateError('Game session not initialized');
+   }
    await _familyApiService.saveUserGratitudeCategory(
        _gameId!,
        _selectedProfiles[_getCurrentSuperHeroIndex()].userId,
        gratitude?.displayText ?? '');

Also applies to: 104-107

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (11)
lib/features/family/features/gratitude-summary/domain/repositories/parent_summary_repository.dart (2)

4-7: Add class documentation.

Consider adding a doc comment to describe the repository's purpose and responsibilities.

+/// Repository responsible for managing parent summary related operations,
+/// including bedtime responsibilities and game summaries.
+/// Communicates with the Family API service for data operations.
 class ParentSummaryRepository {
   ParentSummaryRepository(this._familyAPIService);
   final FamilyAPIService _familyAPIService;

4-24: Consider extracting an interface for better testability.

To improve testability and maintain a clear contract, consider extracting an interface for this repository:

abstract class IParentSummaryRepository {
  Future<bool> assignBedtimeResponsibility({
    required String childGuid,
    required String parentGuid,
    required bool yes,
  });
  
  Future<ParentSummaryItem> fetchLatestGameSummary();
}

class ParentSummaryRepository implements IParentSummaryRepository {
  // ... existing implementation
}

This would make it easier to:

  1. Mock the repository in tests
  2. Swap implementations if needed
  3. Maintain a clear contract for the repository's responsibilities
lib/features/family/features/gratitude-summary/bloc/bedtime_responsibility_cubit.dart (1)

25-36: Improve error handling and documentation

While the fallback mechanism is good, the current implementation has room for improvement:

  1. Silent error handling makes debugging difficult
  2. The comment about the fallback mechanism could be more detailed

Consider this enhanced implementation:

 void saveDecision({required bool yes}) {
   try {
     _summaryRepository.assignBedtimeResponsibility(
       childGuid: childGuid,
       parentGuid: _familyAuthRepository.getCurrentUser()!.guid,
       yes: yes,
     );
   } catch (e) {
-    // it's okay if we fail, the fall-back is that both parents get winddown-time push notifications
+    // Silent failure is intentional:
+    // If the assignment fails, the system falls back to sending wind-down time
+    // push notifications to both parents as a safety mechanism.
+    // Consider logging the error for debugging purposes:
+    log.warning('Failed to assign bedtime responsibility', error: e);
   }
   emitCustom(yes);
 }
lib/features/family/app/family_pages.dart (2)

25-27: Fix inconsistent formatting of enum entry

The assignBedtimeResponsibility entry has inconsistent formatting compared to other multi-line enum entries in the file.

Apply this formatting to match the style of other entries:

-  assignBedtimeResponsibility(
-      path: 'assign-bedtime-responsibility',
-      name: 'ASSIGN-BEDTIME-RESPONSIBILITY'),
+  assignBedtimeResponsibility(
+    path: 'assign-bedtime-responsibility',
+    name: 'ASSIGN-BEDTIME-RESPONSIBILITY',
+  ),

25-28: Consider grouping related pages together

The new entries assignBedtimeResponsibility and parentSummary appear to be related to the gratitude summary feature. Consider grouping them together under a comment section like other features (e.g., "//coin flow", "//school event mode").

Suggested organization:

+  //gratitude summary
   assignBedtimeResponsibility(
     path: 'assign-bedtime-responsibility',
     name: 'ASSIGN-BEDTIME-RESPONSIBILITY',
   ),
   parentSummary(
     path: 'parent-summary',
     name: 'PARENT-SUMMARY',
   ),
lib/features/family/features/gratitude-summary/presentation/pages/bedtime_reponsibility_screen.dart (5)

33-39: Consider improving state management.

A few suggestions to enhance state management:

  1. Consider moving the _showButtons flag into the cubit state for centralized state management
  2. Make the text variable final and update it through state changes instead of direct mutation
- late String text;
- bool _showButtons = true;
+ final String initialText;

40-50: Add null safety check for string interpolation.

The text initialization could potentially fail if widget.name is null. Consider adding a null safety check:

- text = 'Are you putting ${widget.name} to bed?';
+ text = 'Are you putting ${widget.name ?? 'your child'} to bed?';

72-74: Avoid hard-coded delays in navigation.

The 1800ms delay for navigation could lead to inconsistent user experience across different devices and scenarios. Consider:

  1. Making the delay duration configurable
  2. Using an animation or transition instead of a raw delay
- Future.delayed(const Duration(milliseconds: 1800), () {
+ Future.delayed(const Duration(milliseconds: kNavigationDelay), () {

87-95: Improve avatar accessibility.

The AvatarWidget has a non-functional onTap handler and transparent text color, which might affect accessibility:

AvatarWidget(
    uiModel: AvatarUIModel(
      avatarUrl: widget.imageUrl,
      text: widget.name,
    ),
    circleSize: 100,
-   textColor: Colors.transparent,
-   onTap: () {}),
+   textColor: Colors.white,
+   onTap: null),

102-117: Consider adding button state management.

The buttons should be disabled during state transitions to prevent multiple submissions:

FunButton(
  text: 'Yes',
  onTap: _cubit.onClickedYes,
+ enabled: !state.isLoading,
  analyticsEvent: AnalyticsEvent(
    AmplitudeEvents.whoDoesBedtimePushYesClicked,
  ),
),
lib/features/family/app/injection.dart (1)

14-16: Consider renaming directory to follow Dart conventions

The directory name 'gratitude-summary' uses a hyphen which deviates from Dart's conventional naming patterns. Consider using underscore or camelCase instead (e.g., 'gratitude_summary' or 'gratitudeSummary').

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 44e89e6 and 77babf2.

📒 Files selected for processing (6)
  • lib/features/family/app/family_pages.dart (1 hunks)
  • lib/features/family/app/family_routes.dart (2 hunks)
  • lib/features/family/app/injection.dart (4 hunks)
  • lib/features/family/features/gratitude-summary/bloc/bedtime_responsibility_cubit.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/domain/repositories/parent_summary_repository.dart (1 hunks)
  • lib/features/family/features/gratitude-summary/presentation/pages/bedtime_reponsibility_screen.dart (1 hunks)
🔇 Additional comments (9)
lib/features/family/features/gratitude-summary/domain/repositories/parent_summary_repository.dart (3)

1-4: LGTM! Clean imports and class declaration.

The imports are specific and the class name follows the repository pattern convention.


8-18: Consider improving parameter naming and error handling.

The previous review comment about parameter naming and error handling is still applicable.


20-23: Add error handling and response validation.

The previous review comment about error handling and response validation is still applicable.

lib/features/family/features/gratitude-summary/bloc/bedtime_responsibility_cubit.dart (1)

10-13: LGTM! Clean dependency injection implementation

The repositories are properly initialized using dependency injection, and the fields are correctly marked as final.

lib/features/family/features/gratitude-summary/presentation/pages/bedtime_reponsibility_screen.dart (2)

1-31: LGTM! Class structure and imports are well organized.

The widget's parameters are properly defined and required, following Flutter best practices.


2-3: Verify navigation and analytics integration.

Let's verify that:

  1. The route FamilyPages.reflectIntro is properly defined
  2. The analytics events are properly configured

Also applies to: 65-65

✅ Verification successful

Navigation and analytics integration verified successfully.

Both the route and analytics events are properly configured:

  • Route reflectIntro is defined in family_pages.dart
  • Analytics events whoDoesBedtimePushYesClicked and whoDoesBedtimePushNoClicked are properly configured in amplitude_events.dart
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the route is defined
rg "reflectIntro" -A 5 "lib/features/family/app/family_pages.dart"

# Verify analytics events
rg "whoDoesBedtimePushYesClicked|whoDoesBedtimePushNoClicked" "lib/core/enums/amplitude_events.dart"

Length of output: 411

lib/features/family/app/injection.dart (1)

154-158: LGTM: Repository registration follows established patterns

The ParentSummaryRepository is correctly registered as a singleton and follows the same dependency injection pattern as other repositories in the codebase.

lib/features/family/app/family_routes.dart (2)

167-171: LGTM! Simple and clean route implementation

The ParentSummaryScreen route implementation is straightforward and doesn't require any parameters.


158-166: ⚠️ Potential issue

Add parameter validation for AssignBedtimeResponsibilityScreen route

The route accepts query parameters without validation. Missing or invalid parameters default to empty strings, which could lead to UI issues.

Add parameter validation as follows:

 GoRoute(
   path: FamilyPages.assignBedtimeResponsibility.path,
   name: FamilyPages.assignBedtimeResponsibility.name,
+  redirect: (context, state) {
+    final childName = state.uri.queryParameters['childName'];
+    final childId = state.uri.queryParameters['childId'];
+    if (childName?.isEmpty ?? true || childId?.isEmpty ?? true) {
+      return FamilyPages.profileSelection.path;
+    }
+    return null;
+  },
   builder: (context, state) => AssignBedtimeResponsibilityScreen(
     name: state.uri.queryParameters['childName'] ?? '',
     childGuid: state.uri.queryParameters['childId'] ?? '',
     imageUrl: state.uri.queryParameters['pictureUrl'] ?? '',
   ),
 ),

Comment on lines +7 to +8
class AssignBedtimeResponsibilityCubit extends CommonCubit<dynamic, bool> {
AssignBedtimeResponsibilityCubit() : super(const BaseState.initial());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider replacing dynamic with a specific state type

Using dynamic as a type parameter reduces type safety. Consider creating a dedicated state class to represent the bedtime responsibility state more explicitly.

-class AssignBedtimeResponsibilityCubit extends CommonCubit<dynamic, bool> {
+class AssignBedtimeResponsibilityCubit extends CommonCubit<BedtimeResponsibilityState, bool> {

You'll need to create a state class like:

class BedtimeResponsibilityState {
  final String? childGuid;
  final bool? assigned;
  // Add other relevant state properties

  const BedtimeResponsibilityState({
    this.childGuid,
    this.assigned,
  });
}

try {
_summaryRepository.assignBedtimeResponsibility(
childGuid: childGuid,
parentGuid: _familyAuthRepository.getCurrentUser()!.guid,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential null user scenario

The use of the null assertion operator (!) on getCurrentUser() could lead to runtime crashes if the user is not authenticated.

Consider adding proper null checking:

-parentGuid: _familyAuthRepository.getCurrentUser()!.guid,
+final currentUser = _familyAuthRepository.getCurrentUser();
+if (currentUser == null) {
+  throw StateError('No authenticated user found');
+}
+parentGuid: currentUser.guid,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
parentGuid: _familyAuthRepository.getCurrentUser()!.guid,
final currentUser = _familyAuthRepository.getCurrentUser();
if (currentUser == null) {
throw StateError('No authenticated user found');
}
parentGuid: currentUser.guid,

Comment on lines +15 to +19
late final String childGuid;

void init(String childGuid) {
this.childGuid = childGuid;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation and consider constructor initialization

The current implementation has several potential issues:

  1. No validation of the childGuid parameter
  2. Possible runtime errors if init() is not called before accessing childGuid

Consider this safer implementation:

-late final String childGuid;
+final String childGuid;

-AssignBedtimeResponsibilityCubit() : super(const BaseState.initial());
+AssignBedtimeResponsibilityCubit({required this.childGuid}) 
+    : super(const BaseState.initial()) {
+  if (childGuid.isEmpty) {
+    throw ArgumentError('childGuid cannot be empty');
+  }
+}

-void init(String childGuid) {
-  this.childGuid = childGuid;
-}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
late final String childGuid;
void init(String childGuid) {
this.childGuid = childGuid;
}
final String childGuid;
AssignBedtimeResponsibilityCubit({required this.childGuid})
: super(const BaseState.initial()) {
if (childGuid.isEmpty) {
throw ArgumentError('childGuid cannot be empty');
}
}

Comment on lines +61 to +80
body: BaseStateConsumer(
cubit: _cubit,
onCustom: (context, decisionWasYes) {
if (decisionWasYes) {
context.goNamed(FamilyPages.reflectIntro.name);
} else {
setState(() {
text =
'No worries, we’ll remind the other parent to play before bed!';
_showButtons = false;
});
Future.delayed(const Duration(milliseconds: 1800), () {
context.pop();
});
}
},
onInitial: (context) {
return body(text);
},
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and loading states.

The BaseStateConsumer doesn't appear to handle error states or loading states. Consider:

  1. Adding error handling with user feedback
  2. Showing a loading indicator during state transitions
body: BaseStateConsumer(
  cubit: _cubit,
+ onError: (context, error) {
+   return ErrorWidget(
+     message: 'Failed to update bedtime responsibility',
+     onRetry: () => _cubit.init(widget.childGuid),
+   );
+ },
+ onLoading: (context) {
+   return const Center(child: CircularProgressIndicator());
+ },
  onCustom: (context, decisionWasYes) {

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +69 to +70
..registerFactory(ParentSummaryCubit.new)
..registerFactory(AssignBedtimeResponsibilityCubit.new)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing import for AssignBedtimeResponsibilityCubit

The AssignBedtimeResponsibilityCubit is registered but its import statement is missing. This could lead to compilation errors.

Add this import at the top of the file:

+import 'package:givt_app/features/family/features/gratitude-summary/bloc/bedtime_responsibility_cubit.dart';

Committable suggestion skipped: line range outside the PR's diff.

@@ -11,6 +11,9 @@ import 'package:givt_app/features/family/features/bedtime/blocs/setup_bedtime_cu
import 'package:givt_app/features/family/features/edit_profile/repositories/edit_profile_repository.dart';
import 'package:givt_app/features/family/features/giving_flow/collectgroup_details/repositories/organisation_details_repository.dart';
import 'package:givt_app/features/family/features/giving_flow/create_transaction/repositories/create_transaction_repository.dart';
import 'package:givt_app/features/family/features/gratitude-summary/bloc/parent_summary_cubit.dart';
import 'package:givt_app/features/family/features/gratitude-summary/bloc/yesno-cubit.dart';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

The import appears to be incorrect or the file is missing

The verification reveals:

  1. The imported file yesno-cubit.dart doesn't exist in the gratitude-summary directory structure
  2. All dependency registrations in the injection file consistently use registerLazySingleton for cubits
  3. Only repositories occasionally use registerSingleton

The import statement appears to be referencing a non-existent file. Either:

  • The file name is incorrect
  • The file was removed but the import wasn't cleaned up
  • The file is yet to be created
🔗 Analysis chain

Verify YesNoCubit registration and singleton pattern consistency

Two architectural considerations:

  1. The YesNoCubit is imported but not registered in the DI container. If it's needed, it should be registered.
  2. There's an inconsistent pattern in repository registration - some use registerLazySingleton while others use registerSingleton. Consider standardizing this approach.

Let's verify the YesNoCubit usage:

Also applies to: 69-158


The previous search might have failed due to case sensitivity or path issues. Let's verify both the YesNoCubit existence and registration patterns to make a more informed decision.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for YesNoCubit usage in the codebase
rg "YesNoCubit" --type dart

Length of output: 27


Script:

#!/bin/bash
# Let's try a case-insensitive search and include the file path pattern
rg -i "yesno.*cubit" --type dart

# Also search for any registration patterns in the injection.dart file
rg "register.*Singleton" "lib/features/family/app/injection.dart"

# Let's also check the gratitude-summary directory structure
fd . "lib/features/family/features/gratitude-summary"

Length of output: 3802

@Daniela510 Daniela510 merged commit 92316e3 into develop Nov 27, 2024
1 check passed
@Daniela510 Daniela510 deleted the summary-hackathon branch November 27, 2024 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants