Skip to content

Conversation

@RaulRinconX
Copy link
Contributor

@RaulRinconX RaulRinconX commented Dec 3, 2024

This pull request includes several changes to enhance the profile image handling functionality and update permissions in the Android manifest files. The most important changes include adding new permissions, updating the profile bloc to handle profile images, and modifying the profile screen to allow users to pick and display profile images.

Permissions Update:

Profile Bloc Enhancements:

Profile Screen Modifications:

Dependency Updates:

  • pubspec.yaml: Added dependencies for camera, image_picker, path_provider, and permission_handler packages. [1] [2]

Minor Changes:

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for selecting and displaying profile images.
    • Introduced functionality for caching profile images when offline.
    • Enhanced support ticket submission with local caching and retry capabilities.
  • Permissions

    • Added permissions for accessing media, external storage, and camera functionality.
  • Dependencies

    • Integrated new packages for camera access, image selection, file path management, and permission handling.

@RaulRinconX RaulRinconX added the enhancement New feature or request label Dec 3, 2024
@RaulRinconX RaulRinconX added this to the Sprint 4 milestone Dec 3, 2024
@RaulRinconX RaulRinconX self-assigned this Dec 3, 2024
@RaulRinconX RaulRinconX linked an issue Dec 3, 2024 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2024

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes in this pull request primarily involve updates to the AndroidManifest.xml files and modifications to the profile management functionality in the Flutter application. New permissions have been added to facilitate media access and camera usage, while the application is set to use a legacy storage model on older Android versions. Additionally, enhancements to the profile handling logic allow for caching and managing profile images, improving user experience when offline.

Changes

File Path Change Summary
android/app/src/main/AndroidManifest.xml Added permissions: READ_MEDIA_IMAGES, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, CAMERA. Updated <application> with android:requestLegacyExternalStorage="true".
android/app/src/profile/AndroidManifest.xml Added permissions: READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, CAMERA.
lib/features/profile/presentation/bloc/profile_bloc.dart Added imports for dart:io and path_provider. Enhanced _onUpdateProfile to handle cached profile images.
lib/features/profile/presentation/screens/profile_screen.dart Added variables and methods for image handling: _profileImage, _pickImage, _loadProfileImage. Updated _saveProfile for caching profile images.
lib/features/support/presentation/bloc/support_bloc.dart Enhanced _onSubmitTicket and _onRetryCachedTickets for better error handling and local caching of support tickets.
pubspec.yaml Added dependencies: camera, image_picker, path_provider, permission_handler.

Possibly related PRs

  • Resolves. feature/gps-location-util-#35 #40: This PR adds permissions for location access in the AndroidManifest.xml, which is related to the main PR's changes that also involve modifying permissions in the same file.
  • add app icon #13: This PR adds permissions for external storage and camera access in the AndroidManifest.xml, which is relevant as the main PR also modifies permissions in the same file.
  • Fix/build error #48: This PR adds a permission for internet access in the AndroidManifest.xml, which is related to the main PR's changes that involve modifying permissions in the same file.

Suggested labels

bug

🐰 In the garden of code, we hop and play,
Permissions added, brightening the day.
With images cached, and profiles to show,
Our app's now ready, let the users glow!
From camera clicks to storage so wide,
In this digital burrow, we take pride! 🌟


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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (5)
android/app/src/main/AndroidManifest.xml (2)

7-10: Review permission declaration strategy

The combination of both modern (READ_MEDIA_IMAGES) and legacy storage permissions is good for backward compatibility. However:

  1. Consider adding maxSdkVersion="32" to legacy storage permissions to restrict them on newer Android versions
  2. Document the minimum supported Android version in the README
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

Line range hint 18-19: ⚠️ Security: Exposed Google Maps API key

The Google Maps API key is exposed in plain text in the manifest. This key should be protected to prevent unauthorized usage.

Recommendations:

  1. Move the API key to a secure location (e.g., encrypted properties file)
  2. Implement API key restrictions in Google Cloud Console
  3. Consider using different keys for debug and release builds

Would you like assistance in implementing a secure key management solution?

lib/features/profile/presentation/bloc/profile_bloc.dart (2)

Line range hint 73-83: Enhance error handling and logging

The error handling could be more specific and provide better context to help with debugging.

Consider this improvement:

 } catch (e) {
-  Logger().e('Failed to update profile: $e');
+  Logger().e('Failed to update profile', error: e, stackTrace: StackTrace.current);
   emit(
-    ProfileError('Please connect to the internet to update your profile'),
+    ProfileError(
+      'Failed to update profile: ${e is NetworkException ? 'Please check your internet connection' : 'An unexpected error occurred'}'
+    ),
   );
 }

Line range hint 1-150: Consider standardizing error handling patterns

The error handling approach varies across different methods. Consider implementing a consistent error handling strategy across the bloc.

Suggestions:

  1. Create custom exceptions for different error scenarios (e.g., ProfileUpdateException, CacheException)
  2. Implement a centralized error handling method
  3. Consider using a Result type for better error handling

Example implementation:

class ProfileException implements Exception {
  final String message;
  final dynamic originalError;
  
  ProfileException(this.message, [this.originalError]);
  
  @override
  String toString() => 'ProfileException: $message';
}

Future<void> _handleError(
  String operation,
  dynamic error,
  Emitter<ProfileState> emit,
) {
  final message = _getErrorMessage(error);
  Logger().e('Failed to $operation', error: error, stackTrace: StackTrace.current);
  emit(ProfileError(message));
}
lib/features/profile/presentation/screens/profile_screen.dart (1)

170-181: Enhance profile image UI with loading and error states

The current implementation lacks loading indicators and error state handling.

Consider this improved implementation:

 GestureDetector(
   onTap: _pickImage,
-  child: CircleAvatar(
-    radius: 50,
-    backgroundImage: _profileImage != null
-        ? FileImage(_profileImage!)
-        : null,
-    child: _profileImage == null
-        ? const Icon(Icons.camera_alt, size: 50)
-        : null,
+  child: Stack(
+    children: [
+      CircleAvatar(
+        radius: 50,
+        backgroundImage: _profileImage != null
+            ? FileImage(_profileImage!)
+            : null,
+        onBackgroundImageError: (e, stack) {
+          Logger().e('Error loading profile image', error: e, stackTrace: stack);
+        },
+        child: _profileImage == null
+            ? const Icon(Icons.camera_alt, size: 50)
+            : null,
+      ),
+      if (_isLoading)
+        const Positioned.fill(
+          child: CircularProgressIndicator(),
+        ),
+    ],
   ),
 ),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c054180 and 0016a68.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • android/app/src/main/AndroidManifest.xml (1 hunks)
  • android/app/src/profile/AndroidManifest.xml (1 hunks)
  • lib/features/profile/presentation/bloc/profile_bloc.dart (3 hunks)
  • lib/features/profile/presentation/screens/profile_screen.dart (9 hunks)
  • lib/features/support/presentation/bloc/support_bloc.dart (0 hunks)
  • pubspec.yaml (2 hunks)
💤 Files with no reviewable changes (1)
  • lib/features/support/presentation/bloc/support_bloc.dart
🔇 Additional comments (2)
pubspec.yaml (1)

31-31: Verify dependency version compatibility

The new dependencies align with the profile image feature requirements. However:

  1. The camera package version ^0.11.0+2 is quite old. Consider upgrading to the latest stable version for better Android 13+ support
  2. Ensure permission_handler implementation includes proper error handling and fallbacks

Let's check for the latest stable versions and any known security issues:

Also applies to: 53-53, 61-62

lib/features/profile/presentation/bloc/profile_bloc.dart (1)

4-4: LGTM: Required imports added for file operations

The new imports for dart:io and path_provider are correctly added to support profile image caching functionality.

Also applies to: 15-15

Comment on lines +7 to +9
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Storage permissions need to be updated for modern Android compatibility

The verification reveals several issues that need to be addressed:

  • The main AndroidManifest.xml already includes both legacy storage permissions and the modern READ_MEDIA_IMAGES permission, causing redundancy
  • The app uses android:requestLegacyExternalStorage="true" which forces the legacy storage model
  • The permission handling code in profile_screen.dart still uses the deprecated Permission.storage API

Recommendations:

  • Remove READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE from profile/AndroidManifest.xml as they're redundant
  • Remove android:requestLegacyExternalStorage="true" from the application tag
  • Update the permission request in profile_screen.dart to use Permission.photos instead of Permission.storage
🔗 Analysis chain

Consider using scoped storage and modern permission model

While the permissions are correctly declared, consider the following security recommendations:

  1. For Android 13+ (API 33+), prefer using READ_MEDIA_IMAGES instead of broad storage permissions
  2. For Android 10+ (API 29+), consider implementing scoped storage instead of using legacy storage permissions
  3. Ensure runtime permission requests are properly implemented in the Flutter code

Let's verify the target SDK version and storage implementation:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check build.gradle for targetSdkVersion and storage-related configurations
rg "targetSdkVersion|compileSdkVersion|requestLegacyExternalStorage" android/app/build.gradle

Length of output: 183


Script:

#!/bin/bash
# Check flutter.properties or local.properties for SDK version values
rg -l "compileSdkVersion|targetSdkVersion" android/

# Check main AndroidManifest.xml for storage-related configurations
cat android/app/src/main/AndroidManifest.xml

# Search for storage permission handling in Dart code
rg "requestPermission|getPermission|storage|imagePicker" lib/

Length of output: 3996

Comment on lines +55 to +61

final Directory cacheDir = await getTemporaryDirectory();
final String imagePath = '${cacheDir.path}/profile_image.png';
if (File(imagePath).existsSync()) {
// Handle cached profile image
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incomplete implementation of cached profile image handling

The code checks for the existence of a cached profile image but doesn't implement any handling logic. This could lead to inconsistent state management.

Consider implementing the image handling logic:

 final Directory cacheDir = await getTemporaryDirectory();
 final String imagePath = '${cacheDir.path}/profile_image.png';
 if (File(imagePath).existsSync()) {
+  try {
+    final File cachedImage = File(imagePath);
+    // Handle the cached image, e.g., emit a state with the cached image
+    emit(ProfileLoaded(event.updatedProfile, cachedImagePath: imagePath));
+  } catch (e) {
+    Logger().e('Failed to handle cached profile image: $e');
+  }
-  // Handle cached profile image
 }

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

Comment on lines +116 to +121
if (_profileImage != null) {
// Save the profile image to cache
final Directory cacheDir = await getTemporaryDirectory();
final String imagePath = '${cacheDir.path}/profile_image.png';
await _profileImage!.copy(imagePath);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consolidate profile saving logic and add cleanup

There's duplicate code for saving profile data to cache, and no cleanup of old cached files.

Consider extracting the caching logic to a separate method and adding cleanup:

+ Future<void> _saveToCache(UserProfile profile, {File? image}) async {
+   try {
+     final Directory cacheDir = await getTemporaryDirectory();
+     
+     // Clean up old cache files
+     final cacheFiles = await cacheDir.list().where((entity) => 
+       entity.path.contains('profile')).toList();
+     for (var file in cacheFiles) {
+       await file.delete();
+     }
+     
+     // Save new profile data
+     final String profilePath = '${cacheDir.path}/profile.json';
+     await File(profilePath).writeAsString(profile.toMap().toString());
+     
+     // Save new image if provided
+     if (image != null) {
+       final String imagePath = '${cacheDir.path}/profile_image.png';
+       await image.copy(imagePath);
+     }
+   } catch (e) {
+     Logger().e('Error saving to cache', error: e, stackTrace: StackTrace.current);
+     throw Exception('Failed to save profile to cache');
+   }
+ }

Update the profile saving logic to use this method:

 if (internetConnectionBloc.state is DisconnectedInternet) {
-  final Directory cacheDir = await getTemporaryDirectory();
-  final String profilePath = '${cacheDir.path}/profile.json';
-  await File(profilePath).writeAsString(updatedProfile.toMap().toString());
+  await _saveToCache(updatedProfile, image: _profileImage);
   // ... show snackbar
 }

Also applies to: 231-251

Comment on lines +80 to +88
Future<void> _loadProfileImage() async {
final Directory cacheDir = await getTemporaryDirectory();
final String imagePath = '${cacheDir.path}/profile_image.png';
if (File(imagePath).existsSync()) {
setState(() {
_profileImage = File(imagePath);
});
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling to profile image loading

The image loading implementation lacks error handling and proper cleanup.

Consider this improved implementation:

 Future<void> _loadProfileImage() async {
+  try {
     final Directory cacheDir = await getTemporaryDirectory();
     final String imagePath = '${cacheDir.path}/profile_image.png';
-    if (File(imagePath).existsSync()) {
+    final file = File(imagePath);
+    if (await file.exists()) {
       setState(() {
         _profileImage = File(imagePath);
       });
     }
+  } catch (e) {
+    Logger().e('Error loading profile image', error: e, stackTrace: StackTrace.current);
+    // Don't show error to user as this is a silent operation
   }
 }

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

Comment on lines 55 to 78
Future<void> _pickImage() async {
try {
// Check storage permission
if (await Permission.storage.request().isGranted) {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);

if (image != null) {
setState(() {
_profileImage = File(image.path);
});

// Save the profile image to cache
final Directory cacheDir = await getTemporaryDirectory();
final String imagePath = '${cacheDir.path}/profile_image.png';
await _profileImage!.copy(imagePath);
}
} else {
print('Storage permission not granted.');
}
} catch (e) {
print('Error picking image: $e');
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve image picking implementation

Several improvements needed in the image picking implementation:

  1. Error handling uses print instead of proper logging
  2. Missing image validation (size, format)
  3. Permission handling could be more robust

Consider this improved implementation:

 Future<void> _pickImage() async {
   try {
-    // Check storage permission
-    if (await Permission.storage.request().isGranted) {
+    final status = await Permission.storage.request();
+    if (status.isGranted) {
       final ImagePicker picker = ImagePicker();
-      final XFile? image = await picker.pickImage(source: ImageSource.gallery);
+      final XFile? image = await picker.pickImage(
+        source: ImageSource.gallery,
+        maxWidth: 1024,
+        maxHeight: 1024,
+        imageQuality: 80,
+      );

       if (image != null) {
+        // Validate image size
+        final fileSize = await image.length();
+        if (fileSize > 5 * 1024 * 1024) { // 5MB limit
+          throw Exception('Image size too large. Please select an image under 5MB.');
+        }
+
         setState(() {
           _profileImage = File(image.path);
         });

         // Save the profile image to cache
         final Directory cacheDir = await getTemporaryDirectory();
         final String imagePath = '${cacheDir.path}/profile_image.png';
-        await _profileImage!.copy(imagePath);
+        await _profileImage!.copy(imagePath).catchError((error) {
+          throw Exception('Failed to cache profile image: $error');
+        });
       }
     } else {
-      print('Storage permission not granted.');
+      throw Exception('Storage permission required to select profile image');
     }
   } catch (e) {
-    print('Error picking image: $e');
+    Logger().e('Error picking image', error: e, stackTrace: StackTrace.current);
+    if (mounted) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(content: Text(e.toString())),
+      );
+    }
   }
 }
📝 Committable suggestion

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

Suggested change
Future<void> _pickImage() async {
try {
// Check storage permission
if (await Permission.storage.request().isGranted) {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
_profileImage = File(image.path);
});
// Save the profile image to cache
final Directory cacheDir = await getTemporaryDirectory();
final String imagePath = '${cacheDir.path}/profile_image.png';
await _profileImage!.copy(imagePath);
}
} else {
print('Storage permission not granted.');
}
} catch (e) {
print('Error picking image: $e');
}
}
Future<void> _pickImage() async {
try {
final status = await Permission.storage.request();
if (status.isGranted) {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 1024,
maxHeight: 1024,
imageQuality: 80,
);
if (image != null) {
// Validate image size
final fileSize = await image.length();
if (fileSize > 5 * 1024 * 1024) { // 5MB limit
throw Exception('Image size too large. Please select an image under 5MB.');
}
setState(() {
_profileImage = File(image.path);
});
// Save the profile image to cache
final Directory cacheDir = await getTemporaryDirectory();
final String imagePath = '${cacheDir.path}/profile_image.png';
await _profileImage!.copy(imagePath).catchError((error) {
throw Exception('Failed to cache profile image: $error');
});
}
} else {
throw Exception('Storage permission required to select profile image');
}
} catch (e) {
Logger().e('Error picking image', error: e, stackTrace: StackTrace.current);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
}
}
}

@RaulRinconX RaulRinconX requested a review from ma-r-s December 3, 2024 01:23
@RaulRinconX RaulRinconX merged commit f04d7b8 into main Dec 3, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: DONE

Development

Successfully merging this pull request may close these issues.

feature/profile-image

3 participants