-
Notifications
You must be signed in to change notification settings - Fork 4
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
Improvement #65
Improvement #65
Conversation
WalkthroughThe pull request introduces several updates across multiple files, primarily focusing on the management of Dart package dependencies, localization improvements, error handling, and UI component modifications. Key changes include updating the Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant App
participant ErrorScreen
participant PlaceHolderScreen
User->>App: Triggers action (e.g., load media)
App->>ErrorScreen: Error occurs
ErrorScreen-->>User: Display error message
User->>ErrorScreen: Clicks retry
ErrorScreen->>App: Retry action
App->>PlaceHolderScreen: No media available
PlaceHolderScreen-->>User: Display placeholder UI
Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (1)
🔇 Additional comments (1)app/ios/Runner.xcodeproj/project.pbxproj (1)
Setting Let's verify this is consistent with other Flutter projects: Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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: 11
🧹 Outside diff range and nitpick comments (29)
app/lib/ui/flow/media_metadata_details/media_metadata_details.dart (1)
119-127
: Consider improving the source icons presentation.The source icons could benefit from better spacing and accessibility:
if (media.sources.contains(AppMediaSource.googleDrive)) SvgPicture.asset( Assets.images.icGoogleDrive, width: 20, + semanticsLabel: 'Google Drive', ), + const SizedBox(width: 8), if (media.sources.contains(AppMediaSource.dropbox)) SvgPicture.asset( Assets.images.icDropbox, width: 20, + semanticsLabel: 'Dropbox', ),data/lib/services/dropbox_services.dart (1)
38-46
: Reduce code duplication in error handlingMultiple methods contain similar code for handling non-200 status codes by throwing
SomethingWentWrongError
. Consider refactoring this into a helper method to improve maintainability.Example refactoring:
void _handleError(Response res) { throw SomethingWentWrongError( statusCode: res.statusCode, message: 'An unexpected error occurred. Please try again later.', ); }Then replace the error handling in methods:
- if (res.statusCode == 200) { - // Handle success - } else { - throw SomethingWentWrongError( - statusCode: res.statusCode, - message: res.statusMessage, - ); - } + if (res.statusCode == 200) { + // Handle success + } else { + _handleError(res); + }Also applies to: 77-98, 135-138, 289-292, 313-316, 336-339, 355-358
app/lib/ui/flow/media_preview/media_preview_view_model.dart (2)
343-343
: Address the TODO: Handle cases with no media to previewThe TODO comment indicates that the scenario where there is no media to preview is not handled. It's important to implement this logic to enhance user experience.
Do you want me to implement the logic to navigate the user back when there is no media to preview, or open a GitHub issue to track this task?
194-200
: Refactor repetitive error handling in media actionsThe methods contain similar
try-catch
blocks for error handling and logging. Refactoring this into a helper method will reduce code duplication and improve maintainability.Example of creating a helper method:
Future<void> _performAction(Future<void> Function() action, String errorMessage) async { try { state = state.copyWith(actionError: null); await action(); } catch (e, s) { state = state.copyWith(actionError: e); _logger.e( errorMessage, error: e, stackTrace: s, ); } }Then modify your methods to use this helper:
Future<void> deleteMediaFromLocal(String id) async { await _performAction( () => _localMediaService.deleteMedias([id]), "MediaPreviewStateNotifier: unable to delete media from local", ); }Also applies to: 217-223, 240-246, 251-270, 274-293, 297-314, 318-337
app/lib/ui/flow/home/home_screen_view_model.dart (1)
432-458
: Refactor repetitive error handling in cloud media operationsThe methods
uploadToGoogleDrive
,downloadFromGoogleDrive
, anddownloadFromDropbox
contain similartry-catch
blocks for error handling and logging. Consider creating a helper method to handle exceptions consistently, improving code maintainability.Example of creating a helper method:
Future<void> _performCloudAction(Future<void> Function() action, String errorMessage) async { try { state = state.copyWith(actionError: null); await action(); } catch (e, s) { state = state.copyWith(actionError: e); _logger.e( errorMessage, error: e, stackTrace: s, ); } }Then refactor your methods:
Future<void> uploadToGoogleDrive() async { await _performCloudAction( () async { if (state.googleAccount == null) return; final selectedMedias = state.selectedMedias.values .where((media) => media.sources.contains(AppMediaSource.local)) .toList(); _backUpFolderId ??= await _googleDriveService.getBackUpFolderId(); _mediaProcessRepo.uploadMedia( medias: selectedMedias, provider: MediaProvider.googleDrive, folderId: _backUpFolderId!, ); }, "HomeViewStateNotifier: unable to upload to Google Drive", ); }Also applies to: 492-517, 521-546
app/lib/ui/flow/home/components/no_local_medias_access_screen.dart (1)
Line range hint
15-29
: Consider adding error handling for openAppSettings()While the migration to PlaceHolderScreen looks good, the
openAppSettings()
call should include error handling in case the settings cannot be opened.action: PlaceHolderScreenAction( onPressed: () async { - await openAppSettings(); + try { + final opened = await openAppSettings(); + if (opened) { + notifier.loadMedias(reload: true); + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.something_went_wrong_error)), + ); + } + } - notifier.loadMedias(reload: true); }, title: context.l10n.common_open_settings, ),app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart (2)
Line range hint
29-34
: Consider adding retry functionalityThe error state could benefit from a retry option rather than just displaying an error message.
body: PlaceHolderScreen( title: context.l10n.unable_to_load_media_error, message: context.l10n.unable_to_load_media_message, + action: PlaceHolderScreenAction( + onPressed: () => ref.read(networkImagePreviewStateNotifierProvider.notifier).reload(), + title: context.l10n.common_retry, + ), ),
39-42
: Consolidate duplicate PlaceHolderScreen codeThe same PlaceHolderScreen configuration is used in two places. Consider extracting it to a method to avoid duplication.
+PlaceHolderScreen _buildErrorScreen(BuildContext context) { + return PlaceHolderScreen( + title: context.l10n.unable_to_load_media_error, + message: context.l10n.unable_to_load_media_message, + action: PlaceHolderScreenAction( + onPressed: () => ref.read(networkImagePreviewStateNotifierProvider.notifier).reload(), + title: context.l10n.common_retry, + ), + ); +} -return PlaceHolderScreen( - title: context.l10n.unable_to_load_media_error, - message: context.l10n.unable_to_load_media_message, -); +return _buildErrorScreen(context);app/pubspec.yaml (1)
91-91
: Add newline at end of fileAdd a newline character at the end of the file to comply with YAML best practices.
- assets/images/ +
🧰 Tools
🪛 yamllint (1.35.1)
[error] 91-91: no new line character at the end of file
(new-line-at-end-of-file)
app/lib/components/error_screen.dart (2)
9-13
: Add documentation for the public widgetConsider adding documentation comments to describe the widget's purpose, parameters, and usage examples.
+/// A widget that displays error states with appropriate visuals and actions. +/// +/// This widget handles different types of errors and displays them using a consistent UI. +/// It specifically handles [NoConnectionError] with a dedicated screen, while other errors +/// are displayed with a general error screen. +/// +/// Parameters: +/// - [error]: The error object to be displayed +/// - [onRetryTap]: Callback function when the retry button is tapped class ErrorScreen extends StatelessWidget { final Object error; final VoidCallback onRetryTap; const ErrorScreen({super.key, required this.error, required this.onRetryTap});
26-30
: Extract hardcoded dimensions to constantsConsider extracting the hardcoded icon dimensions to named constants for better maintainability.
+ static const double _iconSize = 120.0; + Widget _noInternetConnectionScreen(BuildContext context) => PlaceHolderScreen( icon: SvgPicture.asset( Assets.images.icNoInternet, - height: 120, - width: 120, + height: _iconSize, + width: _iconSize, ),Also applies to: 40-44
app/lib/domain/handlers/deep_links_handler.dart (2)
Line range hint
20-34
: Extract handleDeepLink to a separate methodConsider extracting the handleDeepLink function to improve readability and testability.
static Future<void> observeDeepLinks({ required ProviderContainer container, }) async { final appLinks = container.read(appLinksProvider); final logger = container.read(loggerProvider); - Future<void> handleDeepLink(Uri link) async { - if (link.toString().contains(RedirectURL.auth) && - link.queryParameters['code'] != null) { - // Set the dropbox token from the code - final authService = container.read(authServiceProvider); - await authService.setDropboxTokenFromCode( - code: link.queryParameters['code']!, - ); - - final dropboxService = container.read(dropboxServiceProvider); - await Future.wait([ - dropboxService.setCurrentUserAccount(), - dropboxService.setFileIdAppPropertyTemplate(), - ]); - } - } + try { + final initialLink = await appLinks.getInitialLink(); + if (initialLink != null && !kDebugMode) { + await _handleDeepLink(initialLink, container); + } + } catch (e, s) { + logger.e( + "DEEP LINK ERROR: Failed to handle deep link", + error: e, + stackTrace: s, + ); + } + appLinks.uriLinkStream.listen( + (link) => _handleDeepLink(link, container), + onError: (e, s) { + logger.e( + "DEEP LINK ERROR: Failed to handle deep link", + error: e, + stackTrace: s, + ); + }, + ); + } + + static Future<void> _handleDeepLink( + Uri link, + ProviderContainer container, + ) async { + if (link.toString().contains(RedirectURL.auth) && + link.queryParameters['code'] != null) { + final authService = container.read(authServiceProvider); + await authService.setDropboxTokenFromCode( + code: link.queryParameters['code']!, + ); + + final dropboxService = container.read(dropboxServiceProvider); + await Future.wait([ + dropboxService.setCurrentUserAccount(), + dropboxService.setFileIdAppPropertyTemplate(), + ]); + } }
38-39
: Clarify debug mode conditionThe debug mode check could be more explicit about its purpose.
- if (initialLink != null && !kDebugMode) handleDeepLink(initialLink); + // Skip deep link handling in debug mode to prevent interference with development + if (initialLink != null && !kDebugMode) { + handleDeepLink(initialLink); + }app/lib/components/place_holder_screen.dart (1)
13-20
: Consider adding documentation for the component's usage patterns.The
PlaceHolderScreen
is well-implemented but could benefit from documentation explaining its various use cases (error states, empty states, loading states, etc.) since it's used across multiple screens.Add documentation like:
+/// A versatile placeholder screen component that can be used for various states: +/// - Error states with an error icon and message +/// - Empty states with custom icons +/// - Loading states with loading indicators +/// - No-access states with permission messages class PlaceHolderScreen extends StatelessWidget {app/lib/ui/flow/home/components/no_internet_connection_hint.dart (2)
16-22
: Consider adding null safety for account checks.While the logic works, the account checks could be more explicit about null handling.
Consider this more explicit approach:
- !value.hasInternet && - (value.dropboxAccount != null || value.googleAccount != null), + !value.hasInternet && + ((value.dropboxAccount?.isNotEmpty ?? false) || + (value.googleAccount?.isNotEmpty ?? false)),
27-28
: Fix inconsistent margin definition.The margin is using a non-const constructor while padding uses const.
Apply this fix:
- margin: EdgeInsets.all(16), + margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),data/lib/errors/app_error.dart (1)
Line range hint
19-29
: Consider preserving DioException contextThe simplified DioException handling in
AppError.fromError
loses valuable context by treating all DioExceptions asSomethingWentWrongError
. Consider handling common DioException cases separately (e.g., timeout, network errors) for better error reporting.} else if (error is DioException) { + switch (error.type) { + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + case DioExceptionType.receiveTimeout: + return TimeoutError(message: error.message); + case DioExceptionType.badResponse: + return SomethingWentWrongError( + message: error.message, + statusCode: error.response?.statusCode, + ); + default: return SomethingWentWrongError( message: error.message, statusCode: error.response?.statusCode, ); + } }app/lib/ui/flow/media_preview/components/download_require_view.dart (1)
Line range hint
55-77
: Consider extracting progress indicator widgetThe progress indicator stack with circular progress and icon could be extracted into a reusable component to improve maintainability.
+ class DownloadProgressIndicator extends StatelessWidget { + final double? progress; + final Color backgroundColor; + final Color foregroundColor; + final double size; + + const DownloadProgressIndicator({ + required this.progress, + required this.backgroundColor, + required this.foregroundColor, + required this.size, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + children: [ + AppCircularProgressIndicator( + backgroundColor: backgroundColor, + color: foregroundColor, + strokeWidth: 6, + size: size, + value: progress, + ), + Icon( + CupertinoIcons.cloud_download, + color: foregroundColor, + size: size * 0.53, + ), + ], + ); + } + }app/lib/ui/flow/home/components/sign_in_hint.dart (2)
16-18
: Consider using select for fine-grained reactivityThe widget watches entire account objects when it only needs to know if they exist.
- final googleAccount = ref.watch(googleUserAccountProvider); - final dropboxAccount = ref.watch(AppPreferences.dropboxCurrentUserAccount); + final hasGoogleAccount = ref.watch( + googleUserAccountProvider.select((value) => value != null) + ); + final hasDropboxAccount = ref.watch( + AppPreferences.dropboxCurrentUserAccount.select((value) => value != null) + );
77-82
: Consider separating navigation and state updatesThe sign-in button combines navigation and state updates. Consider extracting this into a method for better maintainability.
+ void _handleSignIn(BuildContext context, WidgetRef ref) { + AccountRoute().push(context); + ref.read(AppPreferences.signInHintShown.notifier).state = true; + } // Usage: - onPressed: () { - AccountRoute().push(context); - ref.read(AppPreferences.signInHintShown.notifier).state = true; - }, + onPressed: () => _handleSignIn(context, ref),app/lib/ui/flow/media_transfer/media_transfer_screen.dart (2)
Line range hint
103-111
: Improve code reusability by extracting common widget logicThe PlaceHolderScreen implementation is duplicated with similar structure. Consider extracting the common logic to reduce duplication.
+ static const _placeholderIconSize = 100.0; + + Widget _buildPlaceholderScreen({ + required String title, + required String message, + required IconData iconData, + required BuildContext context, + }) { + return PlaceHolderScreen( + title: title, + message: message, + icon: Icon( + iconData, + size: _placeholderIconSize, + color: context.colorScheme.containerNormal, + ), + ); + } Widget _uploadList() { // ... if (uploadProcesses.isEmpty) { - return PlaceHolderScreen( - title: context.l10n.empty_upload_title, - message: context.l10n.empty_upload_message, - icon: Icon( - Icons.cloud_upload_outlined, - size: 100, - color: context.colorScheme.containerNormal, - ), + return _buildPlaceholderScreen( + title: context.l10n.empty_upload_title, + message: context.l10n.empty_upload_message, + iconData: Icons.cloud_upload_outlined, + context: context, ); }Also applies to: 144-152
Line range hint
116-119
: Consider extracting duplicated spacing values to constantsThe padding and spacing values are duplicated in both list views.
+ static const _listPadding = EdgeInsets.symmetric(vertical: 16); + static const _listItemSpacing = 16.0; // In both list builders: return ListView.separated( - padding: const EdgeInsets.symmetric(vertical: 16), + padding: _listPadding, itemCount: processes.length, separatorBuilder: (context, index) => const SizedBox( - height: 16, + height: _listItemSpacing, ),Also applies to: 157-160
app/assets/locales/app_en.arb (1)
30-30
: Enhance error messages with specific next stepsWhile the error messages are user-friendly, they could be more specific about what users should do next.
- "something_went_wrong_error": "Oops, something went wrong. Please try again, we're on it and will fix it soon!", + "something_went_wrong_error": "Oops, something went wrong. Try closing and reopening the app, or if the issue persists, contact support. We're working on a fix!", - "save_media_in_gallery_error": "There was an issue saving your media to the gallery. Please try again, we'll have it sorted out shortly", + "save_media_in_gallery_error": "There was an issue saving your media to the gallery. Check your storage space and permissions, then try again. We're working to resolve this!",Also applies to: 34-34
data/lib/services/auth_service.dart (2)
77-89
: Consider adding structured error handling for better user experienceThe simplified error propagation might expose raw Google Sign-in errors to users. Consider wrapping these methods with custom error handling to provide more user-friendly error messages.
Future<void> signInSilently() async { + try { await _googleSignIn.signInSilently(suppressErrors: true); + } catch (e) { + throw AuthenticationError( + message: 'Failed to sign in silently', + cause: e, + ); + } }
Line range hint
148-169
: Enhance token refresh error handling and validationConsider adding token expiration validation and more specific error handling.
Future<void> refreshDropboxToken() async { if (_dropboxTokenController.state != null) { + if (_dropboxTokenController.state!.expires_in.isAfter(DateTime.now())) { + return; // Token still valid + } final res = await _dio.req( DropboxRefreshTokenEndpoint( refreshToken: _dropboxTokenController.state!.refresh_token, clientId: AppSecretes.dropBoxAppKey, clientSecret: AppSecretes.dropBoxAppSecret, ), ); if (res.statusCode == 200) { _dropboxTokenController.state = _dropboxTokenController.state!.copyWith( access_token: res.data['access_token'], expires_in: DateTime.now().add( Duration( seconds: res.data['expires_in'], ), ), token_type: res.data['token_type'], ); return; } } - throw DropboxAuthSessionExpiredError(); + throw DropboxAuthSessionExpiredError( + message: 'Failed to refresh token: ${res.statusMessage}', + ); }data/lib/services/google_drive_service.dart (1)
78-105
: Consider implementing chunked processing for large media listsThe current implementation loads all media files into memory at once. For large collections, this could lead to memory issues.
Consider implementing a chunked processing approach or streaming implementation:
Future<List<AppMedia>> getAllMedias({ required String folder, + int chunkSize = 1000, }) async { final driveApi = await _getGoogleDriveAPI(); bool hasMore = true; String? pageToken; - final List<AppMedia> medias = []; + final List<AppMedia> medias = []; + int processedItems = 0; while (hasMore) { + if (processedItems >= chunkSize) { + // Yield chunk and reset + await Future.delayed(Duration(milliseconds: 100)); + processedItems = 0; + } final response = await driveApi.files.list( q: "'$folder' in parents and trashed=false", $fields: "files(id, name, description, mimeType, thumbnailLink, webContentLink, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, appProperties)", pageSize: 1000, pageToken: pageToken, orderBy: "createdTime desc", ); hasMore = response.nextPageToken != null; pageToken = response.nextPageToken; medias.addAll( (response.files ?? []) .map( (e) => AppMedia.fromGoogleDriveFile(e), ) .toList(), ); + processedItems += response.files?.length ?? 0; } return medias; }app/lib/ui/flow/home/home_screen.dart (1)
133-138
: LGTM! Consider const constructor optimizationThe implementation properly separates connection status and hints. Consider making the Column const for better performance.
- return Column( + return const Column( children: [ - const HomeScreenHints(), - const NoInternetConnectionHint(), + HomeScreenHints(), + NoInternetConnectionHint(), ], );app/lib/ui/flow/media_preview/media_preview_screen.dart (1)
232-234
: LGTM! Good improvement to error handling UX.The switch from
ErrorView
toPlaceHolderScreen
provides a more user-friendly experience when media fails to load. The localized error messages make it more accessible to users.Consider adding a retry action to the
PlaceHolderScreen
to allow users to attempt loading the media again:return PlaceHolderScreen( title: context.l10n.unable_to_load_media_error, message: context.l10n.unable_to_load_media_message, + action: TextButton( + onPressed: () => _notifier.retryLoadMedia(), + child: Text(context.l10n.retry), + ), );data/lib/repositories/media_process_repository.dart (1)
Line range hint
941-946
: LGTM! Improved error handling for Dropbox downloads.Maintains consistency in error handling across all upload/download operations.
Consider extracting the common error handling logic into a helper method to reduce code duplication:
Future<void> _handleTransferError( DioException error, String operation, String provider, Function showNotification, String processId, ) async { if (error.type == DioExceptionType.cancel) { await showNotification('$operation from $provider cancelled'); return; } await showNotification('Failed to $operation from $provider'); await updateDownloadProcessStatus( status: MediaQueueProcessStatus.failed, id: processId, ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (6)
app/assets/images/ic_dropbox.svg
is excluded by!**/*.svg
app/assets/images/ic_error.svg
is excluded by!**/*.svg
app/assets/images/ic_google_drive.svg
is excluded by!**/*.svg
app/assets/images/ic_no_internet.svg
is excluded by!**/*.svg
app/ios/Podfile.lock
is excluded by!**/*.lock
app/lib/gen/assets.gen.dart
is excluded by!**/gen/**
📒 Files selected for processing (44)
.github/workflows/ios_deploy.yml
(1 hunks).idea/libraries/Dart_Packages.xml
(0 hunks).idea/libraries/Flutter_Plugins.xml
(1 hunks)app/assets/locales/app_en.arb
(2 hunks)app/lib/components/error_screen.dart
(1 hunks)app/lib/components/place_holder_screen.dart
(1 hunks)app/lib/domain/extensions/app_error_extensions.dart
(1 hunks)app/lib/domain/extensions/map_extensions.dart
(0 hunks)app/lib/domain/handlers/deep_links_handler.dart
(3 hunks)app/lib/main.dart
(2 hunks)app/lib/ui/flow/accounts/accounts_screen.dart
(2 hunks)app/lib/ui/flow/home/components/app_media_item.dart
(2 hunks)app/lib/ui/flow/home/components/hint_view.dart
(0 hunks)app/lib/ui/flow/home/components/hints.dart
(0 hunks)app/lib/ui/flow/home/components/multi_selection_done_button.dart
(6 hunks)app/lib/ui/flow/home/components/no_internet_connection_hint.dart
(1 hunks)app/lib/ui/flow/home/components/no_local_medias_access_screen.dart
(2 hunks)app/lib/ui/flow/home/components/sign_in_hint.dart
(1 hunks)app/lib/ui/flow/home/home_screen.dart
(3 hunks)app/lib/ui/flow/home/home_screen_view_model.dart
(13 hunks)app/lib/ui/flow/home/home_screen_view_model.freezed.dart
(14 hunks)app/lib/ui/flow/media_metadata_details/media_metadata_details.dart
(1 hunks)app/lib/ui/flow/media_preview/components/download_require_view.dart
(4 hunks)app/lib/ui/flow/media_preview/components/image_preview_screen.dart
(2 hunks)app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart
(3 hunks)app/lib/ui/flow/media_preview/components/top_bar.dart
(6 hunks)app/lib/ui/flow/media_preview/media_preview_screen.dart
(2 hunks)app/lib/ui/flow/media_preview/media_preview_view_model.dart
(9 hunks)app/lib/ui/flow/media_transfer/media_transfer_screen.dart
(3 hunks)app/pubspec.yaml
(1 hunks)data/.flutter-plugins
(1 hunks)data/.flutter-plugins-dependencies
(1 hunks)data/lib/errors/app_error.dart
(3 hunks)data/lib/extensions/date_time_extension.dart
(0 hunks)data/lib/extensions/iterable_extension.dart
(0 hunks)data/lib/handlers/connectivity_handler.dart
(1 hunks)data/lib/repositories/media_process_repository.dart
(4 hunks)data/lib/services/auth_service.dart
(3 hunks)data/lib/services/base/cloud_provider_service.dart
(1 hunks)data/lib/services/dropbox_services.dart
(7 hunks)data/lib/services/google_drive_service.dart
(4 hunks)data/lib/services/local_media_service.dart
(1 hunks)data/lib/storage/provider/preferences_provider.dart
(0 hunks)style/lib/callback/on_visible_callback.dart
(0 hunks)
💤 Files with no reviewable changes (8)
- data/lib/extensions/date_time_extension.dart
- app/lib/domain/extensions/map_extensions.dart
- data/lib/extensions/iterable_extension.dart
- app/lib/ui/flow/home/components/hints.dart
- style/lib/callback/on_visible_callback.dart
- app/lib/ui/flow/home/components/hint_view.dart
- .idea/libraries/Dart_Packages.xml
- data/lib/storage/provider/preferences_provider.dart
✅ Files skipped from review due to trivial changes (7)
- app/lib/ui/flow/accounts/accounts_screen.dart
- app/lib/ui/flow/media_preview/components/top_bar.dart
- app/lib/ui/flow/home/components/app_media_item.dart
- data/lib/services/base/cloud_provider_service.dart
- data/.flutter-plugins
- app/lib/ui/flow/home/components/multi_selection_done_button.dart
- data/.flutter-plugins-dependencies
🧰 Additional context used
🪛 yamllint (1.35.1)
app/pubspec.yaml
[error] 91-91: no new line character at the end of file
(new-line-at-end-of-file)
🔇 Additional comments (18)
app/lib/ui/flow/media_metadata_details/media_metadata_details.dart (1)
120-126
: LGTM! Asset path changes look correct.
The simplified asset paths for Google Drive and Dropbox icons align with the broader asset management restructuring in this PR.
Let's verify that these asset paths exist in the generated Assets class:
✅ Verification successful
Asset paths are correctly defined and accessible
The verification confirms that both icGoogleDrive
and icDropbox
assets are properly defined in the generated Assets class with their corresponding SVG paths:
assets/images/ic_google_drive.svg
assets/images/ic_dropbox.svg
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the asset paths exist in the generated Assets class
# Expected: The asset paths should be defined in the generated code
# Check the generated assets file for these paths
rg "icGoogleDrive|icDropbox" "app/lib/gen/assets.gen.dart"
Length of output: 249
app/lib/domain/extensions/app_error_extensions.dart (1)
11-26
: Consider caching AppError.fromError result
The AppError.fromError(this)
is called for every switch case evaluation. Consider storing the result to avoid multiple conversions.
-switch (AppError.fromError(this).l10nCode) {
+final error = AppError.fromError(this);
+switch (error.l10nCode) {
Also, verify that all error codes are handled:
app/pubspec.yaml (1)
91-91
: Verify asset references after icons directory removal
The removal of assets/images/icons/
directory requires verification of all icon references in the codebase to ensure they've been updated to use the new path structure.
✅ Verification successful
The previous search yielded no results, but we should verify broader image asset usage patterns to ensure we have a complete picture of asset references in the codebase. This will help us determine if the removal of the icons directory is safe and if all references have been properly updated.
Asset directory removal is safe
The removal of assets/images/icons/
directory is safe as all current image assets are properly referenced under assets/images/
directly. The generated assets file shows all images are using the correct path structure:
- app_logo.png
- ic_dropbox.svg
- ic_error.svg
- ic_google_drive.svg
- ic_no_internet.svg
No references to the old assets/images/icons/
path were found in the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for any remaining references to the old icons path
rg -l "assets/images/icons/" --type dart
Length of output: 40
Script:
#!/bin/bash
# Let's first check if there are any image assets referenced in dart files
rg "assets/images/" --type dart -A 2
# Also check for any Image.asset or AssetImage usage
ast-grep --pattern 'Image.asset($_)'
ast-grep --pattern 'AssetImage($_)'
Length of output: 1186
🧰 Tools
🪛 yamllint (1.35.1)
[error] 91-91: no new line character at the end of file
(new-line-at-end-of-file)
app/lib/main.dart (1)
22-31
: LGTM! Robust error handling configuration.
The error handling setup is well-implemented with:
- Appropriate production-only (!kDebugMode) guard
- Comprehensive coverage of both Flutter errors and async errors
- Proper use of fatal flag for crash reporting
Let's verify no debug logs are exposed in production:
✅ Verification successful
Let me check the logger implementation to ensure it respects production vs debug mode configuration.
LGTM! Production-safe logging configuration verified.
The error handling and logging setup is properly configured for production:
- The logger uses
ProductionFilter()
by default, which ensures appropriate filtering - No direct debug print statements found in the codebase
- Error handling correctly routes to Crashlytics in production
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for debug logs that might leak in production
rg "print|debugPrint|console\.log" --type dart
Length of output: 152
Script:
#!/bin/bash
# Check the logger implementation
cat data/lib/log/logger.dart
Length of output: 476
app/lib/components/place_holder_screen.dart (1)
6-11
: LGTM! Well-structured action handler class.
The PlaceHolderScreenAction
class provides a clean interface for handling user actions.
data/lib/errors/app_error.dart (1)
74-81
: Verify impact of AuthSessionExpiredError rename
The renaming of AuthSessionExpiredError
to DropboxAuthSessionExpiredError
makes the error more specific to Dropbox. Ensure all references to this error class have been updated.
✅ Verification successful
All references to AuthSessionExpiredError have been properly updated
The search results show that all occurrences of the error class are using the new name DropboxAuthSessionExpiredError
consistently across the codebase:
- In
data/lib/errors/app_error.dart
: Class definition and constructor - In
data/lib/services/auth_service.dart
: Two throw statements using the new name
No instances of the old name AuthSessionExpiredError
were found, indicating the rename was completed successfully.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for any remaining references to AuthSessionExpiredError
rg "AuthSessionExpiredError" --type dart
Length of output: 367
app/lib/ui/flow/media_preview/components/image_preview_screen.dart (1)
Line range hint 71-76
: LGTM: Improved error handling UI
The replacement of ErrorView with PlaceHolderScreen and the use of localized messages improves consistency and user experience.
app/lib/ui/flow/media_preview/components/download_require_view.dart (2)
3-3
: LGTM: Import change aligns with component migration
The import change correctly reflects the migration from ErrorView
to PlaceHolderScreen
.
Line range hint 80-93
: Verify localization keys
The component uses several localization keys. Let's verify they exist in the localization files.
✅ Verification successful
All localization keys are properly defined
All the localization keys used in the component (download_require_text
, download_require_message
, and common_download
) are correctly defined in the English locale file at app/assets/locales/app_en.arb
. The keys are also consistently used across multiple components in the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for the used localization keys in the l10n files
rg -A 1 "download_require_text|download_require_message|common_download" "app/lib/**/l10n"
Length of output: 148
Script:
#!/bin/bash
# Let's first find the l10n files in the repository
fd "l10n" --type f
# Then search for the localization keys in all files
rg -A 1 "download_require_text|download_require_message|common_download"
Length of output: 2212
app/assets/locales/app_en.arb (1)
58-66
: Well-structured messages with consistent tone
The new messages maintain a friendly, conversational tone while providing clear information. The organization into sections (@_NO_INTERNET_CONNECTION_SCREEN, @_ERROR_SCREEN) improves maintainability.
data/lib/services/auth_service.dart (2)
93-104
: LGTM! Secure implementation of OAuth2 PKCE flow
The implementation correctly uses PKCE flow for enhanced security in the OAuth2 process.
107-137
: LGTM! Robust token handling implementation
The implementation includes proper validation, secure token storage, and specific error handling.
data/lib/services/google_drive_service.dart (1)
184-213
: LGTM! Robust upload implementation with proper error handling
The implementation includes progress tracking, cancellation support, and proper error handling.
app/lib/ui/flow/home/home_screen.dart (1)
91-95
: LGTM! Improved error handling with clear user feedback
The error handling logic properly considers both error state and media availability, providing a clear path for users to retry failed operations.
app/lib/ui/flow/home/home_screen_view_model.freezed.dart (1)
22-22
: LGTM! Generated code changes are consistent.
The addition of the hasInternet
property is properly integrated across all necessary parts of the generated state class, including:
- Property declaration
- CopyWith methods
- Constructor
- toString, equals, and hashCode implementations
Also applies to: 54-54, 86-86, 104-107, 274-274, 298-300, 365-366, 391-391, 416-416, 434-435
data/lib/repositories/media_process_repository.dart (3)
Line range hint 507-512
: LGTM! Improved error handling for Google Drive uploads.
The change to use DioException.type
for cancellation checks is more precise and follows standard Dio error handling practices.
Line range hint 596-601
: LGTM! Improved error handling for Dropbox uploads.
Consistent with the Google Drive changes, using DioException.type
for cancellation checks.
Line range hint 823-828
: LGTM! Improved error handling for Google Drive downloads.
Consistent error handling implementation using DioException.type
.
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: 1
🧹 Outside diff range and nitpick comments (2)
app/lib/components/error_screen.dart (2)
8-12
: Add class and parameter documentationConsider adding documentation to improve code maintainability:
+/// A widget that displays different error states with appropriate visuals and messages. class ErrorScreen extends StatelessWidget { + /// The error object to be displayed. final Object error; + /// Callback function triggered when the retry button is tapped. final VoidCallback onRetryTap; const ErrorScreen({super.key, required this.error, required this.onRetryTap});
24-50
: Reduce code duplication and extract constantsThe screen methods share similar structure and have hard-coded values:
+const double _errorIconSize = 120.0; + +class _ErrorScreenConfig { + final String assetPath; + final String Function(BuildContext) titleGetter; + final String Function(BuildContext) messageGetter; + + const _ErrorScreenConfig({ + required this.assetPath, + required this.titleGetter, + required this.messageGetter, + }); +} + class ErrorScreen extends StatelessWidget { // ... existing code ... - Widget _noInternetConnectionScreen(BuildContext context) => PlaceHolderScreen( - icon: SvgPicture.asset( - Assets.images.icNoInternet, - height: 120, - width: 120, - ), - title: context.l10n.no_internet_connection_title, - message: context.l10n.no_internet_connection_message, - action: PlaceHolderScreenAction( - title: context.l10n.common_retry, - onPressed: onRetryTap, - ), - ); + Widget _buildErrorScreen(BuildContext context, _ErrorScreenConfig config) => + PlaceHolderScreen( + icon: SvgPicture.asset( + config.assetPath, + height: _errorIconSize, + width: _errorIconSize, + ), + title: config.titleGetter(context), + message: config.messageGetter(context), + action: PlaceHolderScreenAction( + title: context.l10n.common_retry, + onPressed: onRetryTap, + ), + ); - Widget _errorScreen(BuildContext context) => PlaceHolderScreen(...); + Widget _noInternetConnectionScreen(BuildContext context) => _buildErrorScreen( + context, + _ErrorScreenConfig( + assetPath: Assets.images.icNoInternet, + titleGetter: (context) => context.l10n.no_internet_connection_title, + messageGetter: (context) => context.l10n.no_internet_connection_message, + ), + ); + + Widget _errorScreen(BuildContext context) => _buildErrorScreen( + context, + _ErrorScreenConfig( + assetPath: Assets.images.icError, + titleGetter: (context) => context.l10n.error_screen_title, + messageGetter: (context) => context.l10n.error_screen_message, + ), + );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
app/lib/components/error_screen.dart
(1 hunks)app/lib/ui/flow/home/components/sign_in_hint.dart
(1 hunks)data/.flutter-plugins-dependencies
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- app/lib/ui/flow/home/components/sign_in_hint.dart
- data/.flutter-plugins-dependencies
🔇 Additional comments (2)
app/lib/components/error_screen.dart (2)
1-7
: LGTM! Well-organized imports
The imports are clean, relevant, and follow good practices by using generated asset constants.
1-51
: Verify complete migration from ErrorView to PlaceHolderScreen
Since PlaceHolderScreen was refactored from ErrorView, let's ensure all usages have been updated.
✅ Verification successful
Migration from ErrorView to PlaceHolderScreen is complete
The verification results show that:
- There are no remaining occurrences of "ErrorView" in the Dart files
- PlaceHolderScreen is being used consistently across the codebase in various screens and components
- The error handling UI has been successfully migrated to use PlaceHolderScreen
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for any remaining usage of ErrorView
rg "ErrorView" --type dart
# Search for new PlaceHolderScreen usage
rg "PlaceHolderScreen" --type dart
Length of output: 2035
# Conflicts: # app/ios/Podfile.lock # data/.flutter-plugins-dependencies
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: 7
🧹 Outside diff range and nitpick comments (6)
app/pubspec.yaml (1)
92-92
: Add newline at end of fileThe YAML file is missing a newline character at the end of the file. While this might not affect functionality, it's a common convention to end files with a newline.
- assets/images/ +
🧰 Tools
🪛 yamllint (1.35.1)
[error] 92-92: no new line character at the end of file
(new-line-at-end-of-file)
style/lib/animations/dismissible_page.dart (1)
Line range hint
32-45
: Consider optimizing gesture update handlingThe current implementation recalculates state and triggers a rebuild on every drag update. Consider adding a minimum displacement threshold before triggering state updates to reduce unnecessary rebuilds.
Here's a suggested optimization:
onVerticalDragUpdate: (DragUpdateDetails details) { - if ((details.globalPosition.dy - _startY) > 0 && widget.enable) { + final newDisplacement = details.globalPosition.dy - _startY; + if (newDisplacement > 0 && widget.enable && + (newDisplacement - displacement).abs() > 1.0) { // 1.0 pixel threshold setState(() { - displacement = details.globalPosition.dy - _startY; + displacement = newDisplacement; percentage = (displacement / widget.threshold).clamp(0, 1); }); widget.onProgress?.call(percentage); } },data/lib/services/local_media_service.dart (1)
Line range hint
1-91
: Consider implementing a consistent error handling strategyThe systematic removal of error handling across all methods in this service is concerning. Consider implementing a consistent error handling strategy that:
- Catches specific exceptions where possible
- Transforms low-level errors into domain-specific ones
- Provides meaningful feedback to users
- Logs errors appropriately for debugging
This will improve app stability and user experience.
Would you like assistance in designing a comprehensive error handling strategy for this service?
app/lib/ui/flow/media_preview/media_preview_view_model.dart (1)
343-343
: Implement Handling for No Media to PreviewThe TODO comment indicates a need to handle cases where there is no media to preview. Implement logic to return the user to the previous screen or display a suitable message when the media list is empty.
Would you like assistance in implementing this functionality?
app/lib/ui/flow/home/components/no_internet_connection_hint.dart (2)
27-27
: Useconst
Constructor forEdgeInsets
Using
const EdgeInsets.all(16)
improves performance by reducing unnecessary object creation.Apply this diff:
- margin: EdgeInsets.all(16), + margin: const EdgeInsets.all(16),
68-68
: Usedouble.infinity
for Unbounded WidthUsing
double.infinity
instead ofdouble.maxFinite
makes the intent clearer when expanding the button to the maximum available width.Apply this diff:
- minimumSize: const Size(double.maxFinite, 40), + minimumSize: const Size(double.infinity, 40),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
app/assets/images/ic_download.svg
is excluded by!**/*.svg
app/assets/images/ic_upload.svg
is excluded by!**/*.svg
app/lib/gen/assets.gen.dart
is excluded by!**/gen/**
app/pubspec.lock
is excluded by!**/*.lock
📒 Files selected for processing (14)
.idea/libraries/Dart_Packages.xml
(16 hunks)app/.gitignore
(1 hunks)app/assets/locales/app_en.arb
(3 hunks)app/lib/ui/flow/home/components/app_media_item.dart
(4 hunks)app/lib/ui/flow/home/components/no_internet_connection_hint.dart
(1 hunks)app/lib/ui/flow/home/home_screen_view_model.dart
(13 hunks)app/lib/ui/flow/media_preview/media_preview_view_model.dart
(9 hunks)app/lib/ui/flow/media_transfer/media_transfer_screen.dart
(3 hunks)app/lib/ui/flow/onboard/onboard_screen.dart
(1 hunks)app/pubspec.yaml
(1 hunks)data/.flutter-plugins-dependencies
(1 hunks)data/lib/handlers/connectivity_handler.dart
(1 hunks)data/lib/services/local_media_service.dart
(1 hunks)style/lib/animations/dismissible_page.dart
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- app/.gitignore
🚧 Files skipped from review as they are similar to previous changes (5)
- app/lib/ui/flow/media_transfer/media_transfer_screen.dart
- app/lib/ui/flow/home/components/app_media_item.dart
- data/.flutter-plugins-dependencies
- .idea/libraries/Dart_Packages.xml
- app/assets/locales/app_en.arb
🧰 Additional context used
🪛 yamllint (1.35.1)
app/pubspec.yaml
[error] 92-92: no new line character at the end of file
(new-line-at-end-of-file)
🔇 Additional comments (9)
app/pubspec.yaml (1)
92-92
: Verify asset references after directory restructuring
The removal of the specific icons directory path suggests a consolidation of assets under the general images directory. Please ensure all icon references in the codebase have been updated accordingly.
✅ Verification successful
Asset references are properly maintained after directory restructuring
Based on the verification results:
- No references to the old
assets/images/icons
path were found in the codebase - All image assets are being accessed through the generated Assets class (e.g.,
Assets.images.appLogo.path
) - The consolidated
assets/images/
directory is properly declared in pubspec.yaml - The asset references in the code are consistent with the new structure
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Search for any remaining references to the old icons directory path
# that might have been missed during the restructuring
# Check for any remaining references to the old icons path
rg -l "assets/images/icons" --type dart
# Check asset references in generated code
rg -l "Assets\.images\.icons\." --type dart
Length of output: 85
Script:
#!/bin/bash
# Let's verify the current asset structure and usage
# Check the actual assets directory structure
fd . assets/
# Look for any Image.asset or AssetImage usage in dart files
rg "Image\.asset|AssetImage" --type dart -C 2
# Check for any asset declarations in pubspec.yaml
cat app/pubspec.yaml
Length of output: 2815
🧰 Tools
🪛 yamllint (1.35.1)
[error] 92-92: no new line character at the end of file
(new-line-at-end-of-file)
style/lib/animations/dismissible_page.dart (1)
Line range hint 1-76
: Verify usage of DismissiblePage widget
Since this is a shared UI component, we should verify all its implementations to ensure they handle the background color opacity correctly.
✅ Verification successful
The initial search shows two files: the widget definition and one usage in media_preview_screen.dart
. Let me verify if there are any other usages and check the implementation details more thoroughly.
No impact on existing implementations
The widget is only used in one place (media_preview_screen.dart
) and it's using the background color property correctly with the surface color from the theme. The opacity handling is internal to the widget and doesn't require any changes in the implementation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for all usages of DismissiblePage widget
rg -l "DismissiblePage" | while read -r file; do
echo "=== $file ==="
# Show the context of DismissiblePage usage
rg -B 2 -A 5 "DismissiblePage" "$file"
done
Length of output: 1392
Script:
#!/bin/bash
# Search for any imports of DismissiblePage
rg "import.*dismissible_page" -B 0 -A 0
# Get full context of the usage in media_preview_screen.dart
rg -B 10 -A 10 "DismissiblePage" "app/lib/ui/flow/media_preview/media_preview_screen.dart"
Length of output: 821
data/lib/services/local_media_service.dart (4)
36-49
:
Restore error handling for getAllLocalMedia method
The removal of error handling makes the application vulnerable to crashes. Multiple async operations could fail:
- PhotoManager.getAssetCount()
- PhotoManager.getAssetListRange()
- Asset to AppMedia conversion
Future<List<AppMedia>> getAllLocalMedia() async {
+ try {
final count = await PhotoManager.getAssetCount();
final assets = await PhotoManager.getAssetListRange(
start: 0,
end: count,
filterOption: FilterOptionGroup(
orders: [const OrderOption(type: OrderOptionType.createDate)],
),
);
final files = await Future.wait(
assets.map(
(asset) => AppMedia.fromAssetEntity(asset),
),
);
return files.nonNulls.toList();
+ } catch (e) {
+ throw AppError.fromError(e);
+ }
}
56-68
:
Add error handling for getLocalMedia method
Similar to getAllLocalMedia, this method performs multiple async operations without error handling, making it susceptible to unhandled exceptions.
Future<List<AppMedia>> getLocalMedia({
required int start,
required int end,
}) async {
+ try {
final assets = await PhotoManager.getAssetListRange(
start: start,
end: end,
filterOption: FilterOptionGroup(
orders: [const OrderOption(type: OrderOptionType.createDate)],
),
);
final files = await Future.wait(
assets.map(
(asset) => AppMedia.fromAssetEntity(asset),
),
);
return files.nonNulls.toList();
+ } catch (e) {
+ throw AppError.fromError(e);
+ }
}
72-72
:
Add error handling for media deletion
Deletion operations should always include error handling to prevent silent failures and inform users of any issues.
Future<List<String>> deleteMedias(List<String> medias) async {
+ try {
return await PhotoManager.editor.deleteWithIds(medias);
+ } catch (e) {
+ throw AppError.fromError(e);
+ }
}
80-91
:
Add error handling for gallery save operations
File operations are particularly prone to failures (insufficient storage, permissions, invalid formats). Error handling is crucial for providing meaningful feedback to users.
Future<AppMedia?> saveInGallery({
required String saveFromLocation,
required AppMediaType type,
}) async {
+ try {
AssetEntity? asset;
if (type.isVideo) {
asset = await PhotoManager.editor.saveVideo(
File(saveFromLocation),
title: saveFromLocation.split('/').last,
);
} else if (type.isImage) {
asset = await PhotoManager.editor.saveImageWithPath(
saveFromLocation,
title: saveFromLocation.split('/').last,
);
}
return asset != null ? AppMedia.fromAssetEntity(asset) : null;
+ } catch (e) {
+ throw AppError.fromError(e);
+ }
}
data/lib/handlers/connectivity_handler.dart (1)
11-34
: Improve Internet Connectivity Check Method
Relying on InternetAddress.lookup('example.com')
may not accurately reflect internet connectivity due to DNS caching. Consider using a more reliable method, such as making a network request to a known endpoint with a timeout.
app/lib/ui/flow/onboard/onboard_screen.dart (2)
Line range hint 1-124
: Well-structured implementation with good practices
The implementation demonstrates good Flutter practices:
- Clean widget composition and organization
- Proper use of localization
- Consistent theming
- Good separation of concerns with private methods
27-27
: Verify consistent color opacity handling across the codebase
The change from withOpacity(0.2)
to withValues(alpha: 0.2)
introduces a custom way of handling color opacity. While both achieve similar results, let's ensure consistency across the codebase.
Consider documenting the custom withValues
extension method if not already done, explaining why it's preferred over the standard withOpacity
.
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: 3
🧹 Outside diff range and nitpick comments (4)
data/lib/services/google_drive_service.dart (4)
78-105
: Consider implementing batch processing for large datasetsThe implementation is correct and handles pagination properly. However, for folders with many files, processing all files at once could consume significant memory.
Consider implementing batch processing to handle large datasets more efficiently:
Future<List<AppMedia>> getAllMedias({ required String folder, + int batchSize = 1000, }) async { final driveApi = await _getGoogleDriveAPI(); bool hasMore = true; String? pageToken; final List<AppMedia> medias = []; while (hasMore) { final response = await driveApi.files.list( q: "'$folder' in parents and trashed=false", $fields: "files(id, name, description, mimeType, thumbnailLink, webContentLink, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, appProperties)", - pageSize: 1000, + pageSize: batchSize, pageToken: pageToken, orderBy: "createdTime desc", ); hasMore = response.nextPageToken != null; pageToken = response.nextPageToken; + + // Process files in smaller chunks to avoid memory spikes + for (var i = 0; i < (response.files?.length ?? 0); i += 100) { + final end = (i + 100 < (response.files?.length ?? 0)) + ? i + 100 + : response.files?.length ?? 0; + medias.addAll( + (response.files?.sublist(i, end) ?? []) + .map((e) => AppMedia.fromGoogleDriveFile(e)) + .toList(), + ); + // Allow event loop to process other tasks + await Future.delayed(Duration.zero); + } - medias.addAll( - (response.files ?? []) - .map( - (e) => AppMedia.fromGoogleDriveFile(e), - ) - .toList(), - ); } return medias; }
184-213
: Add input validation and file size checksWhile the error handling is good, the method could benefit from additional validation.
Consider adding these validations:
Future<AppMedia> uploadMedia({ required String folderId, required String path, String? localRefId, String? mimeType, CancelToken? cancelToken, void Function(int sent, int total)? onProgress, }) async { final localFile = File(path); + + // Validate file existence and size + if (!await localFile.exists()) { + throw FileSystemException('File not found', path); + } + + final fileSize = await localFile.length(); + // Google Drive has a limit of 5TB per file + if (fileSize > 5 * 1024 * 1024 * 1024 * 1024) { + throw ArgumentError('File size exceeds Google Drive limit of 5TB'); + } + final file = drive.File( name: localFile.path.split('/').last, mimeType: mimeType, appProperties: { ProviderConstants.localRefIdKey: localRefId, }, parents: [folderId], ); // Rest of the implementation...
223-237
: Add destination path validationWhile the error handling is good, consider validating the save location.
Add validation for the save location:
Future<void> downloadMedia({ required String id, required String saveLocation, CancelToken? cancelToken, void Function(int sent, int total)? onProgress, }) async { + final saveDir = Directory(saveLocation.substring(0, saveLocation.lastIndexOf('/'))); + if (!await saveDir.exists()) { + await saveDir.create(recursive: true); + } + + final saveFile = File(saveLocation); + if (await saveFile.exists()) { + await saveFile.delete(); + } + final res = await _client.downloadReq( GoogleDriveDownloadEndpoint( id: id, cancellationToken: cancelToken, saveLocation: saveLocation, onProgress: onProgress, ), ); if (res.statusCode == 200) return; throw SomethingWentWrongError( statusCode: res.statusCode, message: res.statusMessage, ); }
245-258
: Add input validation for propertiesThe implementation has good error handling but could benefit from input validation.
Consider adding validation:
Future<void> updateAppProperties({ required String id, required String localRefId, CancelToken? cancelToken, }) async { + if (id.trim().isEmpty) { + throw ArgumentError('File ID cannot be empty'); + } + if (localRefId.trim().isEmpty) { + throw ArgumentError('Local reference ID cannot be empty'); + } + final res = await _client.req( GoogleDriveUpdateAppPropertiesEndpoint( id: id, localFileId: localRefId, ), ); if (res.statusCode == 200) return; throw SomethingWentWrongError( statusCode: res.statusCode, message: res.statusMessage, ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
data/lib/apis/google_drive/google_drive_endpoint.dart
(1 hunks)data/lib/services/google_drive_service.dart
(4 hunks)
🔇 Additional comments (3)
data/lib/apis/google_drive/google_drive_endpoint.dart (2)
117-117
: LGTM! Explicit PATCH method is more appropriate
The change to use HTTP PATCH method aligns with REST conventions and Google Drive API's best practices for partial updates.
121-124
: Verify the appProperties field usage with Google Drive API
The change from "properties" to "appProperties" is correct as it's the appropriate field for application-specific metadata. However, let's verify that this change works as expected with the Google Drive API.
✅ Verification successful
Change to appProperties is correct and consistent
The code review is accurate. After examining the codebase:
- The
GoogleDriveUpdateAppPropertiesEndpoint
class is the only place handling these properties - The class name itself correctly reflects the use of "appProperties"
- The implementation aligns with Google Drive API's best practices by using "appProperties" for application-specific metadata
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Search for any remaining usage of the old "properties" field to ensure consistent updates
# Check for any remaining usage of "properties" field in Google Drive related files
rg -A 3 "properties.*localRefIdKey" --glob "**/*drive*.dart"
# Check for any tests that might need updating
rg -A 3 "properties.*:" --glob "**/*drive*test*.dart"
Length of output: 267
Script:
#!/bin/bash
# Let's try a broader search without glob patterns
# First, find all Google Drive related files
fd "drive.*\.dart$"
# Then search for any properties or appProperties usage in these files
rg -A 3 "\"(app)?properties\":"
Length of output: 221
Script:
#!/bin/bash
# Let's examine the specific file and its test files more closely
cat data/lib/apis/google_drive/google_drive_endpoint.dart
# Also check for any test files that might not follow the exact naming pattern
fd "test.*drive.*\.dart$"
Length of output: 3022
data/lib/services/google_drive_service.dart (1)
108-131
: LGTM! Clean implementation of pagination
The implementation correctly handles pagination with proper field selection and parameter handling.
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: 1
🧹 Outside diff range and nitpick comments (2)
app/lib/ui/flow/media_preview/media_preview_view_model.dart (2)
194-200
: Consider extracting common error handling logicThe error handling pattern is duplicated across deletion methods. Consider extracting it into a reusable helper method.
+ Future<void> _handleError(String operation, Object error, StackTrace stackTrace) { + state = state.copyWith(actionError: error); + _logger.e( + "MediaPreviewStateNotifier: unable to $operation", + error: error, + stackTrace: stackTrace, + ); + } Future<void> deleteMediaFromLocal(String id) async { try { // ... existing code ... - state = state.copyWith(actionError: e); - _logger.e( - "MediaPreviewStateNotifier: unable to delete media from local", - error: e, - stackTrace: s, - ); + await _handleError("delete media from local", e, s); } }Also applies to: 217-223, 240-246
352-354
: Address TODO: Implement navigation for empty previewThe TODO comment indicates missing functionality for handling cases where there are no media items to preview. This is important for user experience.
Would you like me to help implement this feature or create a GitHub issue to track this task?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/lib/ui/flow/media_preview/media_preview_view_model.dart
(9 hunks)
🔇 Additional comments (3)
app/lib/ui/flow/media_preview/media_preview_view_model.dart (3)
3-4
: LGTM: Clean dependency injection implementation
The addition of ConnectivityHandler
and Logger
dependencies follows best practices with proper constructor injection and provider integration.
Also applies to: 36-37, 53-55, 66-67
104-115
: LGTM: Robust error handling implementation
The error handling implementation is comprehensive with proper state management and detailed logging for debugging.
301-323
: Standardize connectivity check pattern
The same connectivity check inconsistency exists in the download methods as in the upload methods.
Also applies to: 327-346
try { | ||
if (state.googleAccount == null) return; | ||
|
||
state = state.copyWith(actionError: null); | ||
|
||
if (_backUpFolderId == null) { | ||
_backUpFolderId = await _googleDriveService.getBackUpFolderId(); | ||
} else { | ||
await _connectivityHandler.checkInternetAccess(); | ||
} | ||
|
||
_mediaProcessRepo.uploadMedia( | ||
folderId: _backUpFolderId!, | ||
medias: [media], | ||
provider: MediaProvider.googleDrive, | ||
); | ||
} catch (e, s) { | ||
state = state.copyWith(actionError: e); | ||
_logger.e( | ||
"MediaPreviewStateNotifier: unable to upload media to Google Drive", | ||
error: e, | ||
stackTrace: s, | ||
); | ||
} |
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
Standardize connectivity check pattern
The connectivity check pattern differs between Google Drive and Dropbox upload methods:
- Google Drive checks connectivity only if
_backUpFolderId
exists - Dropbox checks connectivity immediately
Consider standardizing this pattern to maintain consistency and predictability.
Future<void> uploadMediaInGoogleDrive({required AppMedia media}) async {
try {
if (state.googleAccount == null) return;
state = state.copyWith(actionError: null);
+ await _connectivityHandler.checkInternetAccess();
if (_backUpFolderId == null) {
_backUpFolderId = await _googleDriveService.getBackUpFolderId();
- } else {
- await _connectivityHandler.checkInternetAccess();
}
Also applies to: 278-297
Summary by CodeRabbit
Release Notes
New Features
NoInternetConnectionHint
widget to inform users when there is no internet connectivity.HomeScreenHints
widget to prompt users to sign in with their accounts.Improvements
PlaceHolderScreen
for better user feedback during errors.Bug Fixes
Chores
.gitignore
to include additional directories.