Skip to content

Conversation

@naveenr-egov
Copy link
Collaborator

No description provided.

rachana-egov and others added 30 commits May 23, 2025 15:24
Passing scanned resource details in additionalFields of userAction
Modified date selection logic in attendance flow
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 16, 2025

Walkthrough

This update introduces a comprehensive peer-to-peer (P2P) data sharing feature, new error boundary handling, enhanced attendance and beneficiary management, and expanded configuration and localization mechanisms. Major additions include new pages and blocs for P2P data transfer, device management, and non-mobile user flows. The update also refactors and enriches data models, repositories, and enums to support new business logic, while improving error handling, internationalization, and UI consistency across the application.

Changes

File(s) / Group Change Summary
P2P Data Sharing & Messaging
lib/blocs/peer_to_peer/peer_to_peer.dart, .freezed.dart, lib/models/entities/peer_to_peer/, lib/pages/peer_to_peer/*, lib/widgets/peer_to_peer/file_transfer_animation.dart, lib/router/app_router.dart, .gr.dart, lib/models/data_model.init.dart
Added comprehensive peer-to-peer data transfer feature: new blocs, events, states, message models, enums, animated widgets, and multiple UI pages for device listing, data sending/receiving, and wrapper integration. Updated routing and model initialization accordingly.
Error Handling
lib/blocs/error/error.dart, *.freezed.dart, lib/pages/error_boundary.dart, lib/widgets/digit_error_widget.dart, lib/widgets/error_screen.dart, lib/main.dart, lib/app.dart, lib/pages/authenticated.dart, lib/pages/unauthenticated.dart
Introduced global and local error boundary widgets, new ErrorBloc for error state management, error screen UI, and integrated error handling into main app and page wrappers.
Attendance & Non-Mobile User Enhancements
attendance_management/lib/blocs/attendance_bloc.dart, *.freezed.dart, attendance_individual_bloc.dart, .freezed.dart, lib/pages/non_mobile_user/non_mobile_user_list.dart, lib/widgets/non_mobile_user/, lib/models/entities/attendance_register.dart, *.mapper.dart, attendee.dart, *.mapper.dart, enum_values.dart, *.mapper.dart, scanned_individual_data.dart, *.mapper.dart
Added fetching and display of non-mobile users, enhanced attendance register and attendee models with tags and filtering, introduced attendance sort type, improved attendance marking and draft logic, and added new models for scanned individual data.
Beneficiary & Downsync Improvements
lib/blocs/projects_beneficiary_downsync/project_beneficiaries_downsync.dart, *.freezed.dart, lib/data/local_store/downsync/downsync.dart, lib/data/sync_service_mapper.dart, lib/data/local_store/no_sql/schema/app_configuration.dart, lib/models/app_config/app_config_model.dart, *.freezed.dart, *.g.dart
Added persistent file storage for downsync data, expanded app configuration schema and models, improved sync logic for new entity types, and enhanced enum support for master data.
Localization & Internationalization
lib/blocs/localization/*, *.freezed.dart, lib/utils/localization_delegates.dart, lib/pages/language_selection.dart, lib/pages/boundary_selection.dart, lib/utils/i18_key_constants.dart
Enhanced localization loading (including remote), added new i18n keys and classes for data sharing and non-mobile user flows, updated delegates for new modules, and improved boundary localization with hierarchy prefixes.
Home, Navigation, and UI Enhancements
lib/pages/home.dart, lib/pages/acknowledgement.dart, lib/pages/qr_details_page.dart, lib/pages/project_selection.dart, lib/widgets/showcase/config/home_showcase.dart, showcase_wrappers.dart, lib/widgets/progress_indicator/progress_indicator.dart
Added new home items (data share, beneficiary ID, transit post), improved QR and beneficiary UI, enhanced project selection and showcase, and extended progress indicator customization.
Repository & Constants Updates
lib/utils/constants.dart, lib/widgets/network_manager_provider_wrapper.dart, lib/data/repositories/local/localization.dart, lib/data/repositories/remote/mdms.dart, lib/data/repositories/remote/auth.dart, lib/data/local_store/no_sql/schema/entity_mapper.dart, lib/data/sync_registry.dart
Integrated new repositories for user action and unique ID, improved localization conflict handling, extended MDMS and auth logic, and updated constants for new features and assets.
Test & Package Adjustments
test/integration_test/*, test/models/tenant_boundary_test.dart, attendance_management/CHANGELOG.md, attendance_management.dart, attendance_management.init.dart
Updated and removed tests for deleted models, adjusted package exports/imports, and updated changelogs for attendance management.
Model & Enum Extensions
lib/models/entities/mdms_master_enums.dart, .mapper.dart, lib/models/manual_attendance_reasons/, lib/models/entities/roles_type.dart
Added new master enum values, extended manual attendance reason models, and reformatted roles type enums.
Removed Tenant Boundary Models
lib/models/tenant_boundary/tenant_boundary_model.dart, *.freezed.dart, *.g.dart
Deleted tenant boundary data models and related serialization logic.

Sequence Diagram(s)

Peer-to-Peer Data Transfer (High-Level)

sequenceDiagram
    participant SenderApp as Sender App
    participant NearbyService as Nearby Service
    participant ReceiverApp as Receiver App
    participant PeerToPeerBloc as PeerToPeerBloc

    SenderApp->>PeerToPeerBloc: Trigger DataTransferEvent
    PeerToPeerBloc->>NearbyService: Send chunked data
    NearbyService-->>ReceiverApp: Deliver data chunk
    ReceiverApp->>PeerToPeerBloc: Dispatch DataReceiverEvent
    PeerToPeerBloc->>ReceiverApp: Save data, send acknowledgment
    ReceiverApp-->>NearbyService: Send confirmation
    NearbyService-->>SenderApp: Deliver confirmation
    PeerToPeerBloc->>SenderApp: Update transfer progress
    alt On completion
        PeerToPeerBloc->>NearbyService: Send final acknowledgment
        NearbyService-->>ReceiverApp: Deliver final confirmation
        PeerToPeerBloc->>SenderApp: Emit CompletedDataTransfer
    end
Loading

Error Handling Boundary

sequenceDiagram
    participant WidgetTree as Widget Tree
    participant ErrorBoundary as ErrorBoundary
    participant ErrorBloc as ErrorBloc

    WidgetTree->>ErrorBoundary: Build child widgets
    alt Error occurs in child
        ErrorBoundary->>ErrorBloc: SetErrorEvent
        ErrorBloc->>ErrorBoundary: HasErrorState
        ErrorBoundary->>WidgetTree: Show ErrorScreen
    else No error
        ErrorBoundary->>WidgetTree: Show child widgets
    end
Loading

Poem

🐇✨
New features bloom as springtime brings,
Peer-to-peer data hops on wings!
Error screens now softly land,
While non-mobile users lend a hand.
Attendance sorted, tags in tow,
Boundaries gone, but onward we go—
In fields of code, we leap anew!

— CodeRabbit Rabbit


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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.

@naveenr-egov naveenr-egov requested a review from naveen-egov July 16, 2025 05:47
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: 35

🔭 Outside diff range comments (3)
apps/health_campaign_field_worker_app/lib/models/entities/mdms_master_enums.dart (1)

1-2: Update the Mason template instead of editing generated code
This file (apps/health_campaign_field_worker_app/lib/models/entities/mdms_master_enums.dart) is marked “Generated using mason. Do not modify by hand.” Manual edits here (the four new enum entries) will be overwritten the next time the brick runs.

• apps/health_campaign_field_worker_app/lib/models/entities/mdms_master_enums.dart (lines 1–2, header)
• Remove the manual enum additions from this file
• Add the new entries to the Mason brick or template that generates mdms_master_enums.dart

apps/health_campaign_field_worker_app/lib/widgets/network_manager_provider_wrapper.dart (1)

278-290: Remove duplicate repository provider for UserActionModel.

There are two LocalRepository providers for UserActionModel:

  1. Lines 278-283: Using LocationTrackerLocalBaseRepository
  2. Lines 285-290: Using UserActionLocalRepository

This duplication will cause the second provider to override the first, potentially breaking location tracking functionality.

Either remove one of the providers or use different type parameters to distinguish them:

-      RepositoryProvider<
-          LocalRepository<UserActionModel, UserActionSearchModel>>(
-        create: (_) => LocationTrackerLocalBaseRepository(
-          sql,
-          LocationTrackerOpLogManager(isar),
-        ),
-      ),
-      // INFO Need to add packages here
-      RepositoryProvider<UserActionLocalRepository>(
-        create: (_) => UserActionLocalRepository(
-          sql,
-          UserActionOpLogManager(isar),
-        ),
-      ),
+      // Keep only one provider based on your requirements
+      RepositoryProvider<UserActionLocalRepository>(
+        create: (_) => UserActionLocalRepository(
+          sql,
+          UserActionOpLogManager(isar),
+        ),
+      ),
packages/attendance_management/lib/blocs/attendance_individual_bloc.dart (1)

98-134: Fix inconsistent toggle logic between lists.

The toggle logic is inconsistent between the main attendance list and the search list. When status == 1 && newStatus == 1:

  • Main list: keeps status as 1 (line 108)
  • Search list: sets status to 0 (line 127)

This inconsistency could lead to different states being displayed depending on whether the user is searching or not.

Apply this diff to make the logic consistent:

  } else if (status == 1 && newStatus == 1) {
-   status = 1;
+   status = 0;
  } else if (event.isSingleSession) {
♻️ Duplicate comments (1)
apps/health_campaign_field_worker_app/lib/models/app_config/app_config_model.freezed.dart (1)

1-4: This is a generated file - changes should be made to the source model.

As indicated by the comment, this is auto-generated code from the freezed package. Any modifications should be made to the source app_config_model.dart file, not here.

🧹 Nitpick comments (31)
apps/health_campaign_field_worker_app/test/integration_test/boundary_selection.dart (3)

15-16: Avoid the fixed 5-second time-step in pumpAndSettle.

Passing Duration(seconds: 5) changes the step between frames, not the overall timeout, potentially masking short animations and making flakiness harder to debug. Let the default step (100 ms) apply and use the timeout named argument if you really need a hard cap.

-  await widgetTester.pumpAndSettle(const Duration(seconds: 5));
+  await widgetTester.pumpAndSettle(); // default step, default 10 s timeout

31-45: Scroll into view before tapping dropdown items.

On long forms the target dropdown might be off-screen, causing a StateError in headless runs. Use ensureVisible or scrollUntilVisible before tap.

-    await widgetTester.tap(finder);
+    await widgetTester.ensureVisible(finder);
+    await widgetTester.tap(finder);

52-82: Replace hard-coded UI strings with keys to reduce localisation breakage.

The test relies on English labels ('Download', 'Insufficient Storage', 'Proceed without downloading', 'Ok'). As soon as the app is localised, these strings will change and the test will fail for the wrong reason. Prefer looking up widgets via stable keys or semantics labels.

-  final downloadButton = find.text('Download');
+  final downloadButton = find.byKey(const Key('download_button'));

Repeat for the other occurrences.

packages/attendance_management/CHANGELOG.md (1)

9-11: Fix markdown list indentation.

The unordered list items have incorrect indentation (4 spaces instead of 2 spaces expected).

- * Attendance Revamp
-     * Introduced qr scan for marking attendance
-     * Modified Mark attendance page with new UI
-     * Updated to latest digit_ui_components
+ * Attendance Revamp
+   * Introduced qr scan for marking attendance
+   * Modified Mark attendance page with new UI
+   * Updated to latest digit_ui_components
apps/health_campaign_field_worker_app/lib/blocs/localization/app_localization.dart (1)

35-35: Simplify the return statement.

The ternary operator can be simplified since the condition already evaluates to a boolean.

- return _localizedStrings.isNotEmpty ? true : false;
+ return _localizedStrings.isNotEmpty;
apps/health_campaign_field_worker_app/lib/widgets/digit_error_widget.dart (1)

21-21: Fix typo and spacing in error message.

The error message contains a typo and extra spacing that should be corrected for better user experience.

Apply this diff to fix the issues:

-            'OPPS! : something  is wrong',
+            'OOPS! Something is wrong',
packages/attendance_management/lib/models/entities/scanned_individual_data.dart (1)

7-12: Consider making essential fields non-nullable.

All fields in this model are nullable, which might not align with business requirements. Consider if fields like individualId, name, or qrCreatedTime should be required for proper functionality.

Also consider if age should be an int instead of String for better type safety:

-  final String? age;
+  final int? age;
apps/health_campaign_field_worker_app/lib/app.dart (1)

88-95: Address the TODO comment for better architecture.

The TODO comment indicates this SearchEntityRepository implementation needs to be made more generic. Consider creating a more flexible repository pattern that doesn't hardcode the IndividualOpLogManager.

Do you want me to generate a more generic repository pattern that can work with different OpLogManager types?

apps/health_campaign_field_worker_app/lib/widgets/peer_to_peer/file_transfer_animation.dart (1)

40-55: Consider making dimensions configurable.

The widget uses hardcoded values for sizing and spacing. Consider making these configurable through constructor parameters for better reusability.

 class FileTransferAnimation extends StatefulWidget {
+  final double height;
+  final double phoneIconSize;
+  final double phoneSpacing;
+  final double fileIconSize;
+  
+  const FileTransferAnimation({
+    Key? key,
+    this.height = 100,
+    this.phoneIconSize = 50,
+    this.phoneSpacing = 100,
+    this.fileIconSize = 24,
+  }) : super(key: key);
+
   @override
   _FileTransferAnimationState createState() => _FileTransferAnimationState();
 }

Then update the build method to use these parameters:

     return SizedBox(
-      height: 100,
+      height: widget.height,
       child: Stack(
         alignment: Alignment.center,
         children: [
           // Static Phone Icons
           Row(
             mainAxisAlignment: MainAxisAlignment.center,
             children: [
               Icon(Icons.smartphone,
-                  size: 50, color: DigitTheme.instance.colors.light.primary2),
-              const SizedBox(width: 100), // Distance between the two phones
+                  size: widget.phoneIconSize, color: DigitTheme.instance.colors.light.primary2),
+              SizedBox(width: widget.phoneSpacing), // Distance between the two phones
               Icon(Icons.smartphone,
-                  size: 50, color: DigitTheme.instance.colors.light.primary2),
+                  size: widget.phoneIconSize, color: DigitTheme.instance.colors.light.primary2),
             ],
           ),
           // Moving file icon
           AnimatedBuilder(
             animation: _animation,
             builder: (context, child) {
               return Positioned(
-                left: 50.0 +
+                left: widget.phoneIconSize +
                     (_animation.value *
-                        100), // Move horizontally between phones
+                        widget.phoneSpacing), // Move horizontally between phones
                 child: Icon(
                   Icons.insert_drive_file,
-                  size: 24,
+                  size: widget.fileIconSize,
                   color: DigitTheme.instance.colors.light.textSecondary,
                 ),
               );
             },
           ),
         ],
       ),
     );
apps/health_campaign_field_worker_app/lib/models/entities/peer_to_peer/peer_to_peer_message.dart (2)

5-15: Add documentation for the data model class and its fields.

Consider adding documentation comments to describe the purpose of this model and what each field represents. This will improve code maintainability and help other developers understand the peer-to-peer messaging protocol.

+/// Model representing a peer-to-peer message exchanged between devices
+/// during data transfer operations.
 @MappableClass(ignoreNull: true, discriminatorValue: MappableClass.useAsDefault)
 class PeerToPeerMessageModel with PeerToPeerMessageModelMappable {
+  /// The type of message being sent (e.g., handshake, chunk, confirmation)
   final String messageType;
+  /// The type of confirmation for acknowledgment messages
   final String? confirmationType;
+  /// The current status of the message transfer
   final String? status;
+  /// The actual message content or data payload
   final String message;
+  /// The boundary code for location-specific data transfer
   final String? selectedBoundaryCode;
+  /// Current offset for chunked data transfer
   final int? offset;
+  /// Total count of chunks or items being transferred
   final int? totalCount;
+  /// Transfer progress as a percentage (0.0 to 1.0)
   final double? progress;

16-24: Add trailing comma for better formatting.

   PeerToPeerMessageModel(
       {required this.messageType,
       this.confirmationType,
       this.status,
       required this.message,
       this.selectedBoundaryCode,
       this.offset,
       this.totalCount,
-      this.progress});
+      this.progress,});
apps/health_campaign_field_worker_app/lib/blocs/error/error.dart (1)

17-25: Remove unnecessary async keywords from synchronous methods.

The _onSetError and _onClearError methods don't perform any asynchronous operations, so the async keyword is unnecessary.

   /// Handles setting an error
-  FutureOr<void> _onSetError(SetErrorEvent event, ErrorEmitter emit) async {
+  FutureOr<void> _onSetError(SetErrorEvent event, ErrorEmitter emit) {
     emit(ErrorState.hasError(event.errorMessage));
   }

   /// Handles clearing an error
-  FutureOr<void> _onClearError(ClearErrorEvent event, ErrorEmitter emit) async {
+  FutureOr<void> _onClearError(ClearErrorEvent event, ErrorEmitter emit) {
     emit(const ErrorState.noError());
   }
apps/health_campaign_field_worker_app/lib/models/entities/peer_to_peer/message_types.dart (1)

29-39: Add documentation for MessageStatus enum.

+/// Status of message transfer operations
 @MappableEnum(caseStyle: CaseStyle.upperCase)
 enum MessageStatus {
+  /// Transfer completed successfully
   @MappableValue("success")
   success,
+  /// Transfer failed
   @MappableValue("fail")
   fail,
+  /// Message has been received
   @MappableValue("received")
   received,
+  /// All operations completed
   @MappableValue("completed")
   completed,
 }
apps/health_campaign_field_worker_app/lib/pages/peer_to_peer/data_receiver.dart (2)

49-58: Improve navigation and error message formatting.

The navigation approach and error message formatting could be improved for better UX.

         if (device.state == SessionState.notConnected) {
           if (mounted) {
-            context.router.maybePop();
+            // Ensure we go back to the home screen on disconnection
+            context.router.replaceAll([HomeRoute()]);
             Toast.showToast(
               context,
-              message: localizations.translate(
-                  '${device.deviceName} ${SessionState.notConnected.toString().toUpperCase()}'),
+              message: localizations.translate(i18.dataShare.deviceDisconnected)
+                  .replaceAll('{device}', device.deviceName),
               type: ToastType.error,
             );
           }
         }

Note: You'll need to add the i18.dataShare.deviceDisconnected localization key with a value like "Device {device} has been disconnected".


383-383: Replace hard-coded progress value with a meaningful constant.

The hard-coded value of 0.7 in the failed state seems arbitrary. Consider using 0 or defining a constant that represents the failure state more accurately.

-                                        value: 0.7,
+                                        value: 0.0,  // Reset to 0 on failure
apps/health_campaign_field_worker_app/lib/pages/peer_to_peer/data_share_home.dart (2)

26-30: Remove unnecessary empty initState override.

The initState method doesn't perform any initialization logic and can be removed.

-  @override
-  void initState() {
-    super.initState();
-  }
-

56-203: Consider extracting card creation logic to reduce duplication.

Both send and receive cards share similar structure. Consider creating a reusable widget to improve maintainability.

Example refactor:

Widget _buildActionCard({
  required IconData icon,
  required String title,
  required String description,
  required VoidCallback onPressed,
}) {
  return SizedBox(
    width: MediaQuery.of(context).size.width,
    height: MediaQuery.of(context).size.height * 0.25,
    child: DigitCard(
      padding: const EdgeInsets.only(top: spacer10),
      margin: const EdgeInsets.all(spacer4),
      inline: true,
      onPressed: onPressed,
      children: [
        Center(
          child: Icon(icon,
              size: spacer7,
              color: DigitTheme.instance.colors.light.primary1),
        ),
        // ... rest of the content
      ],
    ),
  );
}
apps/health_campaign_field_worker_app/lib/pages/qr_details_page.dart (1)

215-232: Consider documenting the validation assumption.

While the lack of null checks is acceptable based on higher-level validation, consider adding a comment to document this assumption for future maintainers.

 child: QrImageView(
   data: DataMapEncryptor().encryptWithRandomKey(
+      // Higher-level validation ensures registers and individualList are non-empty
       ScannedIndividualDataModel(
               name: registers.first.individualList!.first
                   .name!.givenName!,
apps/health_campaign_field_worker_app/lib/utils/utils.dart (1)

620-695: Consider adding type annotations for better type safety.

While the implementation is correct, adding explicit type annotations would improve type safety and code clarity.

For example, instead of:

final transformed = <String, dynamic>{

Consider:

final Map<String, dynamic> transformed = {

This applies throughout the function for better type inference and IDE support.

apps/health_campaign_field_worker_app/lib/blocs/peer_to_peer/peer_to_peer.dart (3)

202-203: Address the TODO comment.

This TODO indicates that the DiskSpace.getFreeDiskSpace function should be moved to utils.

Would you like me to help refactor this disk space check into a utility function?


176-325: Break down the large method into smaller, focused methods.

The _handleReceiveEntities method is over 150 lines long and handles multiple responsibilities. Consider breaking it down for better maintainability.

Extract logical sections into separate methods:

Future<void> _handleReceiveEntities(
  DataReceiverEvent event,
  PeerToPeerEmitter emit,
) async {
  try {
    final messageModel = PeerToPeerMessageModelMapper.fromJson(event.data["message"]);
    
    if (messageModel.messageType == MessageTypes.chunk.toValue()) {
      await _handleChunkMessage(messageModel, event, emit);
    } else if (_isFinalTransferConfirmation(messageModel)) {
      await _handleFinalTransfer(event, emit);
    }
  } catch (e) {
    await _handleReceiveError(e, event, emit);
  }
}

Future<void> _handleChunkMessage(
  PeerToPeerMessageModel messageModel,
  DataReceiverEvent event,
  PeerToPeerEmitter emit,
) async {
  // Extract chunk handling logic
}

Future<void> _validateDiskSpace(int? totalCount) async {
  // Extract disk space validation
}

Future<void> _processEntityData(
  Map<String, dynamic> entityData,
  String? selectedBoundaryCode,
) async {
  // Extract entity processing logic
}

171-174: Implement consistent error handling pattern.

Error handling is inconsistent across the bloc. Consider implementing a unified error handling approach.

Create a consistent error handling pattern:

class PeerToPeerError {
  final String message;
  final String? code;
  final dynamic originalError;
  
  PeerToPeerError({
    required this.message,
    this.code,
    this.originalError,
  });
}

// Use consistently:
void _handleError(dynamic error, PeerToPeerEmitter emit, {String? context}) {
  final peerError = PeerToPeerError(
    message: error.toString(),
    code: context,
    originalError: error,
  );
  
  // Log error
  logger.error('P2P Error', error: peerError);
  
  // Emit appropriate state
  if (context == 'transfer') {
    emit(PeerToPeerState.failedToTransfer(error: peerError.message));
  } else {
    emit(PeerToPeerState.failedToReceive(error: peerError.message));
  }
}

Also applies to: 313-324

packages/attendance_management/lib/blocs/attendance_individual_bloc.dart (3)

239-243: Improve search logic for null safety and consistency.

The search logic should handle potential null values and apply consistent case handling.

Apply this diff to improve the search:

  final List<AttendeeModel> result = value.attendanceCollectionModel!
      .where((item) =>
-         item.name!.toLowerCase().contains(event.name.toLowerCase()) ||
-         item.individualNumber!.contains(event.name))
+         (item.name?.toLowerCase().contains(event.name.toLowerCase()) ?? false) ||
+         (item.individualNumber?.toLowerCase().contains(event.name.toLowerCase()) ?? false))
      .toList();

253-275: Optimize sort implementation to avoid unnecessary list copying.

The current implementation always creates a copy of the list even when no sorting is needed (sortType == none).

Apply this diff to optimize:

  final original = loadedState.attendanceCollectionModel ?? [];
- List<AttendeeModel> updatedList = [...original];
+ List<AttendeeModel> updatedList;

- if (event.sortType == AttendanceSortType.presentFirst) {
-   updatedList.sort((a, b) => _getSortValue(a, event.sortType)
-       .compareTo(_getSortValue(b, event.sortType)));
- } else if (event.sortType == AttendanceSortType.absentFirst) {
-   updatedList.sort((a, b) => _getSortValue(a, event.sortType)
-       .compareTo(_getSortValue(b, event.sortType)));
- } // else: no sorting (keep order)
+ if (event.sortType == AttendanceSortType.none) {
+   updatedList = original;
+ } else {
+   updatedList = [...original];
+   updatedList.sort((a, b) => _getSortValue(a, event.sortType)
+       .compareTo(_getSortValue(b, event.sortType)));
+ }

  emit(loadedState.copyWith(

277-288: Consider using named constants for sort priorities.

The sort priority values (0, 1, 2) are magic numbers that could be more maintainable as constants.

Consider defining constants at the class level:

static const int _highPriority = 0;
static const int _mediumPriority = 1;
static const int _lowPriority = 2;

Then use them in the method:

- if (model.status == 1) return 0;
- if (model.status == 0) return 1;
- return 2;
+ if (model.status == 1) return _highPriority;
+ if (model.status == 0) return _mediumPriority;
+ return _lowPriority;
apps/health_campaign_field_worker_app/lib/pages/authenticated.dart (2)

151-178: Refactor duplicated sync refresh watchers.

The two opLog watchers have nearly identical logic and could be combined to reduce duplication.

- isar.opLogs
-     .filter()
-     .createdByEqualTo(userId)
-     .syncedUpEqualTo(false)
-     .watch()
-     .listen(
-   (event) {
-     if (!bloc.isClosed) {
-       triggerSyncRefreshEvent(bloc, userId, event);
-     }
-   },
- );
-
- isar.opLogs
-     .filter()
-     .createdByEqualTo(userId)
-     .syncedUpEqualTo(true)
-     .syncedDownEqualTo(false)
-     .watch()
-     .listen(
-   (event) {
-     if (!bloc.isClosed) {
-       triggerSyncRefreshEvent(bloc, userId, event);
-     }
-   },
- );
+ // Watch for any unsynced operations
+ isar.opLogs
+     .filter()
+     .createdByEqualTo(userId)
+     .filter()
+     .syncedUpEqualTo(false)
+     .or()
+     .group((q) => q
+         .syncedUpEqualTo(true)
+         .syncedDownEqualTo(false))
+     .watch()
+     .listen(
+   (event) {
+     if (!bloc.isClosed) {
+       triggerSyncRefreshEvent(bloc, userId, event);
+     }
+   },
+ );

357-361: Add null safety for connectivity result.

The firstOrNull could return null if the connectivity list is empty.

- final isOnline = connectivityResult.firstOrNull ==
-         ConnectivityResult.wifi ||
-     connectivityResult.firstOrNull ==
-         ConnectivityResult.mobile;
+ final connectionType = connectivityResult.firstOrNull;
+ final isOnline = connectionType == ConnectivityResult.wifi ||
+     connectionType == ConnectivityResult.mobile;
apps/health_campaign_field_worker_app/lib/utils/i18_key_constants.dart (1)

660-661: Fix typo in localization key.

The key has a typo: "MATCHED" should likely be "MATCH" for consistency with other error keys.

- String get noBoundariesMatchedDesc =>
-     'NO_BOUNDARIES_MATCHED_ERROR_DESCRIPTION';
+ String get noBoundariesMatchedDesc =>
+     'NO_BOUNDARIES_MATCH_ERROR_DESCRIPTION';

Also update the corresponding noBoundariesMatchedTitle for consistency:

- String get noBoundariesMatchedTitle => 'NO_BOUNDARIES_MATCHED_ERROR_TITLE';
+ String get noBoundariesMatchedTitle => 'NO_BOUNDARIES_MATCH_ERROR_TITLE';
apps/health_campaign_field_worker_app/lib/pages/home.dart (1)

418-420: Extract hardcoded module name prefixes as constants.

The hardcoded prefixes 'hcm-registrationflow-' and 'hcm-deliveryflow-' should be defined as constants for maintainability.

+  static const String _registrationFlowPrefix = 'hcm-registrationflow-';
+  static const String _deliveryFlowPrefix = 'hcm-deliveryflow-';

   if (isTriggerLocalisation) {
     final moduleName =
-        'hcm-registrationflow-${context.selectedProject.referenceID},hcm-deliveryflow-${context.selectedProject.referenceID}';
+        '$_registrationFlowPrefix${context.selectedProject.referenceID},$_deliveryFlowPrefix${context.selectedProject.referenceID}';
apps/health_campaign_field_worker_app/lib/blocs/project/project.dart (2)

617-628: Improve error handling for form schema enrichment.

The current error handling only prints in debug mode and could crash due to the force unwrap operator.

   } catch (e) {
     emit(
       state.copyWith(
         selectedProject: event.model,
         loading: false,
         syncError: ProjectSyncErrorType.appConfig,
       ),
     );
-    if (kDebugMode) {
-      debugPrint(e.toString());
-    }
+    AppLogger.instance.error(
+      'Failed to enrich form schema',
+      title: 'ProjectBloc',
+      stackTrace: e is Error ? e.stackTrace : null,
+    );
     return;
   }

743-749: Log schema transformation errors instead of silent failure.

Silent failures make debugging difficult in production environments.

   try {
     transformedSchema = transformJson(schemaJson);
   } catch (e, stackTrace) {
-    debugPrint('Schema transformation failed: $e');
-    debugPrint('$stackTrace');
+    AppLogger.instance.error(
+      'Schema transformation failed',
+      title: 'ProjectBloc.storeSchema',
+      stackTrace: stackTrace,
+    );
     transformedSchema = null;
   }

"enabled": true,
});

await localSecureStore.setBoundaryRefetch(true);
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

Remove duplicate call to setBoundaryRefetch.

This call is redundant as the same operation is already performed at line 89.

- await localSecureStore.setBoundaryRefetch(true);
📝 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
await localSecureStore.setBoundaryRefetch(true);
🤖 Prompt for AI Agents
In apps/health_campaign_field_worker_app/lib/blocs/auth/auth.dart at line 99,
remove the duplicate call to localSecureStore.setBoundaryRefetch(true) since the
same call is already made at line 89, making this redundant.

"authorization": "Basic ZWdvdi11c2VyLWNsaWVudDo=",
};

final formData = FormData.fromMap(loginModel.toJson());
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

Remove unused FormData creation.

The FormData.fromMap call is no longer used since the request body now uses the manually encoded string.

- final formData = FormData.fromMap(loginModel.toJson());
📝 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
final formData = FormData.fromMap(loginModel.toJson());
🤖 Prompt for AI Agents
In apps/health_campaign_field_worker_app/lib/data/repositories/remote/auth.dart
at line 20, remove the unused variable declaration that creates FormData from
loginModel.toJson(), since the request body now uses a manually encoded string
and this FormData is no longer needed.

Comment on lines +15 to +27
@MappableEnum(caseStyle: CaseStyle.upperCase)
enum ConfirmationTypes {
@MappableValue("finalTransfer")
finalTransfer,
@MappableValue("chunk")
chunk,
@MappableValue("failed")
failed,
@MappableValue("finalAcknowledgment")
finalAcknowledgment,
@MappableValue("handshake")
handShake
}
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

Fix inconsistent naming and add documentation.

Similar naming inconsistency with "handshake" vs handShake.

+/// Types of confirmations sent during peer-to-peer data transfer
 @MappableEnum(caseStyle: CaseStyle.upperCase)
 enum ConfirmationTypes {
+  /// Confirmation that all data has been transferred
   @MappableValue("finalTransfer")
   finalTransfer,
+  /// Confirmation for individual chunk receipt
   @MappableValue("chunk")
   chunk,
+  /// Indicates transfer failure
   @MappableValue("failed")
   failed,
+  /// Final acknowledgment after successful transfer
   @MappableValue("finalAcknowledgment")
   finalAcknowledgment,
-  @MappableValue("handshake")
+  /// Handshake confirmation
+  @MappableValue("handShake")
   handShake
 }
📝 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
@MappableEnum(caseStyle: CaseStyle.upperCase)
enum ConfirmationTypes {
@MappableValue("finalTransfer")
finalTransfer,
@MappableValue("chunk")
chunk,
@MappableValue("failed")
failed,
@MappableValue("finalAcknowledgment")
finalAcknowledgment,
@MappableValue("handshake")
handShake
}
/// Types of confirmations sent during peer-to-peer data transfer
@MappableEnum(caseStyle: CaseStyle.upperCase)
enum ConfirmationTypes {
/// Confirmation that all data has been transferred
@MappableValue("finalTransfer")
finalTransfer,
/// Confirmation for individual chunk receipt
@MappableValue("chunk")
chunk,
/// Indicates transfer failure
@MappableValue("failed")
failed,
/// Final acknowledgment after successful transfer
@MappableValue("finalAcknowledgment")
finalAcknowledgment,
/// Handshake confirmation
@MappableValue("handShake")
handShake
}
🤖 Prompt for AI Agents
In
apps/health_campaign_field_worker_app/lib/models/entities/peer_to_peer/message_types.dart
between lines 15 and 27, the enum ConfirmationTypes has inconsistent naming for
the "handshake" value, using handShake instead of handshake. Rename the enum
value to handShake to handshake to match the string value and maintain
consistent camelCase naming. Additionally, add documentation comments to each
enum value explaining their purpose for better code clarity.

Comment on lines +5 to +13
@MappableEnum(caseStyle: CaseStyle.upperCase)
enum MessageTypes {
@MappableValue("confirmation")
confirmation,
@MappableValue("chunk")
chunk,
@MappableValue("handshake")
handShake
}
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

Fix inconsistent naming and add documentation.

The mapped value "handshake" doesn't match the camelCase enum value handShake. Also, add documentation to explain the purpose of each enum value.

+/// Types of messages exchanged during peer-to-peer communication
 @MappableEnum(caseStyle: CaseStyle.upperCase)
 enum MessageTypes {
+  /// Confirmation message for acknowledging receipt
   @MappableValue("confirmation")
   confirmation,
+  /// Data chunk message for transferring large data in parts
   @MappableValue("chunk")
   chunk,
-  @MappableValue("handshake")
+  /// Initial handshake message to establish connection
+  @MappableValue("handShake")
   handShake
 }
📝 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
@MappableEnum(caseStyle: CaseStyle.upperCase)
enum MessageTypes {
@MappableValue("confirmation")
confirmation,
@MappableValue("chunk")
chunk,
@MappableValue("handshake")
handShake
}
/// Types of messages exchanged during peer-to-peer communication
@MappableEnum(caseStyle: CaseStyle.upperCase)
enum MessageTypes {
/// Confirmation message for acknowledging receipt
@MappableValue("confirmation")
confirmation,
/// Data chunk message for transferring large data in parts
@MappableValue("chunk")
chunk,
/// Initial handshake message to establish connection
@MappableValue("handShake")
handShake
}
🤖 Prompt for AI Agents
In
apps/health_campaign_field_worker_app/lib/models/entities/peer_to_peer/message_types.dart
between lines 5 and 13, the enum value `handShake` uses inconsistent camelCase
compared to its mapped string "handshake". Rename the enum value to `handshake`
to match the mapped value. Additionally, add documentation comments above each
enum value to explain their purpose clearly.

Comment on lines +200 to +350
children: receivedBoundaries.isNotEmpty
? [
Text(
localizations.translate(i18
.dataShare
.dateReceivedForBoundaries),
style: textTheme.headingM
.copyWith(
color: DigitTheme
.instance
.colors
.light
.primary2),
),
...receivedBoundaries.map((e) =>
Column(
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
Text(
localizations
.translate(e),
style: textTheme.bodyS
.copyWith(
color: DigitTheme
.instance
.colors
.light
.primary2),
),
const DigitDivider(
dividerType:
DividerType.small,
)
],
))
]
: [
Text(
localizations.translate(i18
.dataShare
.noBoundariesMatchedTitle),
style: textTheme.headingM
.copyWith(
color: DigitTheme
.instance
.colors
.light
.primary2),
),
Text(
localizations.translate(i18
.dataShare
.noBoundariesMatchedDesc),
style: textTheme.bodyS
.copyWith(
color: DigitTheme
.instance
.colors
.light
.primary2),
),
]),
)
],
);
},
dataReceived: (receivedBoundaries) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
DigitCard(children: [
Center(
child: Lottie.asset(downloadSuccess,
repeat: false,
height: MediaQuery.of(context)
.size
.height *
0.15),
),
Center(
child: Text(
localizations.translate(i18
.dataShare.receivedSuccessMessage),
style: textTheme.headingM.copyWith(
color: DigitTheme.instance.colors
.light.primary2),
),
),
Center(
child: Padding(
padding:
const EdgeInsets.all(spacer2),
child: ProgressIndicatorContainer(
value: 1,
valueColor:
AlwaysStoppedAnimation<Color>(
theme.colorTheme.alert.success,
),
label: '',
prefixLabel: '',
suffixLabel: '',
height: spacer3,
radius: spacer4,
)),
),
]),
const SizedBox(
height: spacer2,
),
SizedBox(
width: MediaQuery.of(context).size.width,
child: DigitCard(children: [
Text(
localizations.translate(
receivedBoundaries.isNotEmpty
? i18.dataShare
.dateReceivedForBoundaries
: i18.dataShare
.noBoundariesMatchedTitle),
style: textTheme.headingM.copyWith(
color: DigitTheme.instance.colors
.light.primary2),
),
...receivedBoundaries.map((e) => Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
localizations.translate(e),
style: textTheme.bodyS.copyWith(
color: DigitTheme.instance
.colors.light.primary2),
),
const DigitDivider(
dividerType: DividerType.small,
)
],
)),
if (receivedBoundaries.isEmpty)
Text(
localizations.translate(i18.dataShare
.noBoundariesMatchedDesc),
style: textTheme.bodyS.copyWith(
color: DigitTheme.instance.colors
.light.primary2),
),
]),
)
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

Extract duplicated boundary rendering logic into a reusable widget.

The code for rendering boundaries is duplicated between receivingInProgress and dataReceived states.

Create a reusable widget method:

Widget _buildBoundariesCard(List<String> receivedBoundaries) {
  return SizedBox(
    width: MediaQuery.of(context).size.width,
    child: DigitCard(
      children: receivedBoundaries.isNotEmpty
          ? [
              Text(
                localizations.translate(i18.dataShare.dateReceivedForBoundaries),
                style: textTheme.headingM.copyWith(
                  color: DigitTheme.instance.colors.light.primary2,
                ),
              ),
              ...receivedBoundaries.map((e) => Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    localizations.translate(e),
                    style: textTheme.bodyS.copyWith(
                      color: DigitTheme.instance.colors.light.primary2,
                    ),
                  ),
                  const DigitDivider(
                    dividerType: DividerType.small,
                  ),
                ],
              ))
            ]
          : [
              Text(
                localizations.translate(i18.dataShare.noBoundariesMatchedTitle),
                style: textTheme.headingM.copyWith(
                  color: DigitTheme.instance.colors.light.primary2,
                ),
              ),
              Text(
                localizations.translate(i18.dataShare.noBoundariesMatchedDesc),
                style: textTheme.bodyS.copyWith(
                  color: DigitTheme.instance.colors.light.primary2,
                ),
              ),
            ],
    ),
  );
}

Then use it in both places to eliminate duplication.

🤖 Prompt for AI Agents
In
apps/health_campaign_field_worker_app/lib/pages/peer_to_peer/data_receiver.dart
between lines 200 and 350, the code for rendering the received boundaries is
duplicated in the receivingInProgress and dataReceived states. To fix this,
extract the duplicated UI code into a reusable widget method named
_buildBoundariesCard that takes the receivedBoundaries list as a parameter and
returns the appropriate DigitCard widget with the translated texts and styles.
Then replace the duplicated code in both states by calling this new method with
the receivedBoundaries argument, eliminating redundancy and improving
maintainability.

Comment on lines +130 to +145
void handleFooterButtonPress() {
if (connectedDevices.isEmpty) {
Toast.showToast(
context,
message: localizations.translate(i18.dataShare.noDevicesConnected),
type: ToastType.error,
);
return;
} else if (!devices.any((e) => e.state == SessionState.connected)) {
Toast.showToast(
context,
message: localizations.translate(i18.dataShare.noRecipientsSelected),
type: ToastType.error,
);
return;
}
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

Remove redundant connection state check.

The condition at line 138 is redundant because connectedDevices is already filtered to contain only connected devices (line 273).

Apply this diff to simplify the logic:

  if (connectedDevices.isEmpty) {
    Toast.showToast(
      context,
      message: localizations.translate(i18.dataShare.noDevicesConnected),
      type: ToastType.error,
    );
    return;
- } else if (!devices.any((e) => e.state == SessionState.connected)) {
-   Toast.showToast(
-     context,
-     message: localizations.translate(i18.dataShare.noRecipientsSelected),
-     type: ToastType.error,
-   );
-   return;
  }
🤖 Prompt for AI Agents
In
apps/health_campaign_field_worker_app/lib/pages/peer_to_peer/devices_list.dart
between lines 130 and 145, remove the redundant check that verifies if any
device in 'devices' has a connected state, since 'connectedDevices' already
contains only connected devices. Simplify the logic by eliminating the else-if
condition that checks device connection states and rely solely on the
'connectedDevices.isEmpty' check to handle the case of no connected devices.

Comment on lines +392 to +419
var connected = device.state ==
SessionState.connected;
var connecting = device.state ==
SessionState.connecting
? true
: false;

return Container(
margin: const EdgeInsets.only(
left: spacer2,
top: spacer2,
right: spacer2),
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(
Radius.circular(spacer2)),
border: Border.all(
color: connected
? theme.colorTheme.primary
.primary1
: theme.disabledColor)),
child: ListTile(
onTap: connecting
? null
: () {
connecting = true;
handleDeviceTap(device);
},
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 connecting state tracking in device list.

The connecting variable is local to the item builder and doesn't persist across rebuilds. This could lead to UI inconsistencies.

Use the connectingDeviceIds set to track connecting state:

- var connecting = device.state ==
-         SessionState.connecting
-     ? true
-     : false;
+ final isConnecting = connectingDeviceIds.contains(device.deviceId) ||
+     device.state == SessionState.connecting;

  return Container(
    // ...
    child: ListTile(
-     onTap: connecting
+     onTap: isConnecting
          ? null
          : () {
-             connecting = true;
              handleDeviceTap(device);
            },
🤖 Prompt for AI Agents
In
apps/health_campaign_field_worker_app/lib/pages/peer_to_peer/devices_list.dart
between lines 392 and 419, the connecting state is tracked using a local
variable which resets on rebuilds causing UI inconsistencies. Replace the local
connecting variable with a check against the connectingDeviceIds set to persist
the connecting state across rebuilds. Update the onTap handler to add the device
ID to connectingDeviceIds when starting connection and remove it appropriately
when connection completes or fails.

Comment on lines +455 to +509
/// TODO: NEED TO EXTRACT THIS AS UTIL FUNCTION
String? dynamicModule;
final isInRegistrationFlow = context.router.current.name
.contains(RegistrationDeliveryWrapperRoute.name);

if (isInRegistrationFlow) {
final prefs = await SharedPreferences.getInstance();
final schemaJsonRaw = prefs.getString('app_config_schemas');

if (schemaJsonRaw != null) {
final allSchemas =
json.decode(schemaJsonRaw) as Map<String, dynamic>;
final projectId = context.selectedProject.referenceID;

// Initialize empty list to collect modules
final List<String> modules = [];

// Handle registrationflow
final registrationSchemaEntry =
allSchemas['REGISTRATIONFLOW'] as Map<String, dynamic>?;
final registrationSchemaData =
registrationSchemaEntry?['data'];
final registrationFlowName = registrationSchemaData?['name']
?.toString()
.toLowerCase();
if (registrationFlowName != null && projectId != null) {
modules.add('hcm-$registrationFlowName-$projectId');
}

// Handle deliveryflow
final deliverySchemaEntry =
allSchemas['DELIVERYFLOW'] as Map<String, dynamic>?;
final deliverySchemaData = deliverySchemaEntry?['data'];
final deliveryFlowName =
deliverySchemaData?['name']?.toString().toLowerCase();
if (deliveryFlowName != null && projectId != null) {
modules.add('hcm-$deliveryFlowName-$projectId');
}

// Combine into a single string
dynamicModule = modules.join(',');
}
}

final staticModules = localizationModulesList.interfaces
.where(
(element) => element.type == Modules.localizationModule)
.map((e) => e.name.toString())
.followedBy([
'hcm-boundary-${envConfig.variables.hierarchyType}'
]).join(',');

final combinedModules = dynamicModule != null
? '$dynamicModule,$staticModules'
: staticModules;
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

Extract localization module building logic and add error handling.

The TODO comment correctly identifies that this complex logic should be extracted. Additionally, JSON parsing lacks error handling.

Extract to a utility method:

Future<String> _buildLocalizationModules(BuildContext context) async {
  try {
    String? dynamicModule;
    final isInRegistrationFlow = context.router.current.name
        .contains(RegistrationDeliveryWrapperRoute.name);

    if (isInRegistrationFlow) {
      dynamicModule = await _getDynamicModules(context);
    }

    final staticModules = _getStaticModules();
    
    return dynamicModule != null
        ? '$dynamicModule,$staticModules'
        : staticModules;
  } catch (e) {
    // Log error and return static modules as fallback
    debugPrint('Error building localization modules: $e');
    return _getStaticModules();
  }
}

Future<String?> _getDynamicModules(BuildContext context) async {
  final prefs = await SharedPreferences.getInstance();
  final schemaJsonRaw = prefs.getString('app_config_schemas');
  
  if (schemaJsonRaw == null) return null;
  
  try {
    final allSchemas = json.decode(schemaJsonRaw) as Map<String, dynamic>;
    final projectId = context.selectedProject.referenceID;
    final modules = <String>[];
    
    // Extract registration flow module
    _extractFlowModule(allSchemas, 'REGISTRATIONFLOW', projectId, modules);
    
    // Extract delivery flow module
    _extractFlowModule(allSchemas, 'DELIVERYFLOW', projectId, modules);
    
    return modules.isNotEmpty ? modules.join(',') : null;
  } catch (e) {
    debugPrint('Error parsing schemas: $e');
    return null;
  }
}
🤖 Prompt for AI Agents
In apps/health_campaign_field_worker_app/lib/pages/authenticated.dart between
lines 455 and 509, extract the complex localization module building logic into a
separate utility method to improve readability and maintainability. Create a
method that asynchronously builds dynamic modules by reading and parsing JSON
from SharedPreferences with proper try-catch error handling to log parsing
errors and return null on failure. Also, separate static module retrieval into
its own method. Then combine dynamic and static modules in the main method,
returning static modules as a fallback if errors occur. This refactoring removes
the TODO and adds robust error handling around JSON parsing.

Comment on lines +369 to +423
final prefs = await SharedPreferences.getInstance();
final schemaJsonRaw = prefs.getString('app_config_schemas');

if (schemaJsonRaw != null) {
final allSchemas =
json.decode(schemaJsonRaw) as Map<String, dynamic>;

final registrationSchemaEntry =
allSchemas['REGISTRATIONFLOW'] as Map<String, dynamic>?;
final deliverySchemaEntry =
allSchemas['DELIVERYFLOW'] as Map<String, dynamic>?;

final registrationSchemaData = registrationSchemaEntry?['data'];
final deliverySchemaData = deliverySchemaEntry?['data'];

if (registrationSchemaData != null ||
deliverySchemaData != null) {
// Extract templates from both schemas
final regTemplatesRaw = registrationSchemaData?['templates'];
final delTemplatesRaw = deliverySchemaData?['templates'];

final Map<String, dynamic> regTemplateMap =
regTemplatesRaw is Map<String, dynamic>
? regTemplatesRaw
: {};

final Map<String, dynamic> delTemplateMap =
delTemplatesRaw is Map<String, dynamic>
? delTemplatesRaw
: {};

final templates = {
for (final entry
in {...regTemplateMap, ...delTemplateMap}.entries)
entry.key: TemplateConfig.fromJson(
entry.value as Map<String, dynamic>)
};

final registrationConfig = json.encode(registrationSchemaData);
final deliveryConfig = json.encode(deliverySchemaData);

RegistrationDeliverySingleton().setTemplateConfigs(templates);
RegistrationDeliverySingleton()
.setRegistrationConfig(registrationConfig);
RegistrationDeliverySingleton()
.setDeliveryConfig(deliveryConfig);
}

if (isTriggerLocalisation) {
final moduleName =
'hcm-registrationflow-${context.selectedProject.referenceID},hcm-deliveryflow-${context.selectedProject.referenceID}';
triggerLocalization(module: moduleName);
isTriggerLocalisation = false;
}
}
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

⚠️ Potential issue

Add error handling for JSON parsing and schema validation.

The JSON parsing at line 374 could throw exceptions if the stored data is corrupted or malformed. Additionally, the complex nested null checks and type casting make the code hard to maintain.

Consider wrapping the JSON parsing in a try-catch block and extracting the schema loading logic:

 onPressed: () async {
   final prefs = await SharedPreferences.getInstance();
   final schemaJsonRaw = prefs.getString('app_config_schemas');

   if (schemaJsonRaw != null) {
+    try {
       final allSchemas =
           json.decode(schemaJsonRaw) as Map<String, dynamic>;
+    } catch (e) {
+      // Log error and proceed without schemas
+      AppLogger.instance.error('Failed to parse schemas: $e');
+      if (isTriggerLocalisation) {
+        triggerLocalization();
+        isTriggerLocalisation = false;
+      }
+      RegistrationDeliverySingleton()
+          .setHouseholdType(HouseholdType.family);
+      await context.router.push(const RegistrationDeliveryWrapperRoute());
+      return;
+    }

     // Extract templates logic...

Also consider extracting the schema loading logic into a separate method for better readability:

Future<void> _loadAndSetSchemas() async {
  // Move lines 369-415 here
}
🤖 Prompt for AI Agents
In apps/health_campaign_field_worker_app/lib/pages/home.dart between lines 369
and 423, the JSON parsing and schema extraction lack error handling and have
complex nested null checks making the code hard to maintain. Wrap the JSON
decoding and schema extraction logic in a try-catch block to handle potential
exceptions from corrupted or malformed data. Additionally, refactor the schema
loading and setting logic into a separate async method (e.g.,
_loadAndSetSchemas) to improve readability and maintainability, then call this
method from the original location.

Comment on lines +429 to 512
try {
if (context.loggedInUserRoles
.where(
(role) =>
role.code == RolesType.districtSupervisor.toValue() ||
role.code ==
RolesType.distributor
.toValue() || // NOTE: Distributor also fetches registers for getting his team members (Non-Mobile users)
role.code == RolesType.teamSupervisor.toValue(),
)
.toList()
.isNotEmpty) {
final loggedInIndividualId = await localSecureStore.userIndividualId;
late final List<AttendanceRegisterModel> attendanceRegisters;

if (context.loggedInUserRoles
.where(
(role) =>
role.code == RolesType.districtSupervisor.toValue() ||
role.code == RolesType.teamSupervisor.toValue(),
)
.toList()
.isNotEmpty) {
attendanceRegisters = await attendanceRemoteRepository.search(
AttendanceRegisterSearchModel(
staffId: loggedInIndividualId,
referenceId: event.model.id,
localityCode: event.model.address?.boundary,
),
);
} else {
attendanceRegisters = await attendanceRemoteRepository.search(
AttendanceRegisterSearchModel(
attendeeId: loggedInIndividualId,
// Modified attendance search to fetch tagged attendees for non-mobile users
includeTaggedAttendees: true),
);
}
await attendanceLocalRepository.bulkCreate(attendanceRegisters);
for (final register in attendanceRegisters) {
if (register.attendees != null &&
(register.attendees ?? []).isNotEmpty) {
try {
final individuals = await individualRemoteRepository.search(
IndividualSearchModel(
id: register.attendees!
.map((e) => e.individualId!)
.toList(),
),
);
await individualLocalRepository.bulkCreate(individuals);
if (context.loggedInUserRoles
.where(
(role) =>
role.code == RolesType.districtSupervisor.toValue() ||
role.code == RolesType.teamSupervisor.toValue(),
)
.toList()
.isNotEmpty) {
final logs = await attendanceLogRemoteRepository.search(
AttendanceLogSearchModel(
registerId: register.id,
),
);
await attendanceLogLocalRepository.bulkCreate(logs);
}
} catch (_) {
emit(state.copyWith(
projects: [],
loading: false,
syncError: ProjectSyncErrorType.attendance,
));
return;
}
}
}
}
} catch (_) {
emit(state.copyWith(
loading: false,
syncError: ProjectSyncErrorType.attendance,
));
return;
}
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

Refactor nested try-catch blocks for better error handling.

The deeply nested try-catch structure makes error handling difficult to follow and maintain. Consider extracting the attendance fetching logic into separate methods.

+  Future<void> _fetchAttendanceData(ProjectModel project, ProjectEmitter emit) async {
+    try {
+      if (!_shouldFetchAttendance()) return;
+      
+      final loggedInIndividualId = await localSecureStore.userIndividualId;
+      if (loggedInIndividualId == null) {
+        throw Exception('User individual ID not found');
+      }
+      
+      final attendanceRegisters = await _getAttendanceRegisters(
+        loggedInIndividualId, 
+        project
+      );
+      
+      await attendanceLocalRepository.bulkCreate(attendanceRegisters);
+      
+      for (final register in attendanceRegisters) {
+        await _processRegisterAttendees(register);
+      }
+    } catch (e) {
+      emit(state.copyWith(
+        projects: [],
+        loading: false,
+        syncError: ProjectSyncErrorType.attendance,
+      ));
+      rethrow;
+    }
+  }
+  
+  bool _shouldFetchAttendance() {
+    return context.loggedInUserRoles.any((role) => 
+      role.code == RolesType.districtSupervisor.toValue() ||
+      role.code == RolesType.distributor.toValue() ||
+      role.code == RolesType.teamSupervisor.toValue()
+    );
+  }

This refactoring improves readability and makes error handling more maintainable.

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

🤖 Prompt for AI Agents
In apps/health_campaign_field_worker_app/lib/blocs/project/project.dart between
lines 429 and 512, the nested try-catch blocks handling attendance fetching and
related data make the code hard to read and maintain. Refactor by extracting the
inner attendance fetching and processing logic into separate private methods
that handle their own errors. This will flatten the structure, improve
readability, and isolate error handling for each step more cleanly.

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.

7 participants