-
Notifications
You must be signed in to change notification settings - Fork 33
Console 2.0-beta #759
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
base: master
Are you sure you want to change the base?
Console 2.0-beta #759
Conversation
Passing scanned resource details in additionalFields of userAction
Modified date selection logic in attendance flow
Added qr page
Resolved code comments
Console 2.0 - Beta
WalkthroughThis 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
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
Error Handling BoundarysequenceDiagram
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
Poem
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 generatesmdms_master_enums.dartapps/health_campaign_field_worker_app/lib/widgets/network_manager_provider_wrapper.dart (1)
278-290: Remove duplicate repository provider for UserActionModel.There are two
LocalRepositoryproviders forUserActionModel:
- Lines 278-283: Using
LocationTrackerLocalBaseRepository- Lines 285-290: Using
UserActionLocalRepositoryThis 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.dartfile, 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 inpumpAndSettle.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 thetimeoutnamed 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
StateErrorin headless runs. UseensureVisibleorscrollUntilVisiblebeforetap.- 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_componentsapps/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, orqrCreatedTimeshould be required for proper functionality.Also consider if
ageshould be anintinstead ofStringfor 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
_onSetErrorand_onClearErrormethods don't perform any asynchronous operations, so theasynckeyword 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.deviceDisconnectedlocalization 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 failureapps/health_campaign_field_worker_app/lib/pages/peer_to_peer/data_share_home.dart (2)
26-30: Remove unnecessary emptyinitStateoverride.The
initStatemethod 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.getFreeDiskSpacefunction 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
_handleReceiveEntitiesmethod 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
firstOrNullcould 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
noBoundariesMatchedTitlefor 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| @MappableEnum(caseStyle: CaseStyle.upperCase) | ||
| enum ConfirmationTypes { | ||
| @MappableValue("finalTransfer") | ||
| finalTransfer, | ||
| @MappableValue("chunk") | ||
| chunk, | ||
| @MappableValue("failed") | ||
| failed, | ||
| @MappableValue("finalAcknowledgment") | ||
| finalAcknowledgment, | ||
| @MappableValue("handshake") | ||
| handShake | ||
| } |
There was a problem hiding this comment.
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.
| @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.
| @MappableEnum(caseStyle: CaseStyle.upperCase) | ||
| enum MessageTypes { | ||
| @MappableValue("confirmation") | ||
| confirmation, | ||
| @MappableValue("chunk") | ||
| chunk, | ||
| @MappableValue("handshake") | ||
| handShake | ||
| } |
There was a problem hiding this comment.
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.
| @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.
| 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), | ||
| ), | ||
| ]), | ||
| ) |
There was a problem hiding this comment.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| 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); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| /// 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; |
There was a problem hiding this comment.
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.
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
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.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
No description provided.