-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/profile image #75 #76
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
Changes from all commits
c1b9073
902ba0f
07ec2f7
932fec7
0b2e83b
deaa694
0016a68
09a4767
efc785e
16a8e61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| // ignore_for_file: unused_element | ||
|
|
||
| import 'dart:convert'; | ||
| import 'dart:io'; | ||
| import 'package:eco_bites/core/blocs/internet_connection/internet_connection_bloc.dart'; | ||
| import 'package:eco_bites/core/blocs/resettable_mixin.dart'; | ||
| import 'package:eco_bites/core/constants/storage_keys.dart'; | ||
|
|
@@ -11,6 +12,7 @@ import 'package:eco_bites/features/profile/presentation/bloc/profile_event.dart' | |
| import 'package:eco_bites/features/profile/presentation/bloc/profile_state.dart'; | ||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||
| import 'package:logger/logger.dart'; | ||
| import 'package:path_provider/path_provider.dart'; | ||
| import 'package:shared_preferences/shared_preferences.dart'; | ||
|
|
||
| class ProfileBloc extends Bloc<ProfileEvent, ProfileState> | ||
|
|
@@ -50,6 +52,13 @@ class ProfileBloc extends Bloc<ProfileEvent, ProfileState> | |
| ); | ||
| Logger().d('Cached profile: ${event.updatedProfile.toMap()}'); | ||
| Logger().d('Cached profile: ${prefs.getString('cachedProfile')}'); | ||
|
|
||
| final Directory cacheDir = await getTemporaryDirectory(); | ||
| final String imagePath = '${cacheDir.path}/profile_image.png'; | ||
| if (File(imagePath).existsSync()) { | ||
| // Handle cached profile image | ||
| } | ||
|
|
||
|
Comment on lines
+55
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
}
|
||
| emit( | ||
| ProfileError( | ||
| 'No internet connection. Your data will be saved when you are online.', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:eco_bites/core/blocs/internet_connection/internet_connection_bloc.dart'; | ||
| import 'package:eco_bites/core/utils/analytics_service.dart'; | ||
| import 'package:eco_bites/features/auth/presentation/bloc/auth_bloc.dart'; | ||
|
|
@@ -11,7 +13,10 @@ import 'package:eco_bites/features/profile/presentation/bloc/profile_state.dart' | |
| import 'package:firebase_auth/firebase_auth.dart'; | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||
| import 'package:image_picker/image_picker.dart'; | ||
| import 'package:intl/intl.dart'; | ||
| import 'package:path_provider/path_provider.dart'; | ||
| import 'package:permission_handler/permission_handler.dart'; | ||
| import 'package:shared_preferences/shared_preferences.dart'; | ||
|
|
||
| class ProfileScreen extends StatefulWidget { | ||
|
|
@@ -31,10 +36,12 @@ class ProfileScreenState extends State<ProfileScreen> { | |
| CuisineType? _favoriteCuisine; | ||
| DietType? _dietType; | ||
| bool _isInitialized = false; | ||
| File? _profileImage; | ||
|
|
||
| @override | ||
| void initState() { | ||
| super.initState(); | ||
| _loadProfileImage(); | ||
| final String? userId = FirebaseAuth.instance.currentUser?.uid; | ||
| if (userId != null) { | ||
| context.read<ProfileBloc>().add(LoadProfileEvent(userId)); | ||
|
|
@@ -45,6 +52,43 @@ class ProfileScreenState extends State<ProfileScreen> { | |
| } | ||
| } | ||
|
|
||
| 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 { | ||
| // ignore: avoid_print | ||
| print('Storage permission not granted.'); | ||
| } | ||
| } catch (e) { | ||
| // ignore: avoid_print | ||
| print('Error picking image: $e'); | ||
| } | ||
| } | ||
|
|
||
| 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); | ||
| }); | ||
| } | ||
| } | ||
|
Comment on lines
+82
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
}
}
|
||
|
|
||
| // ignore: unused_element | ||
| Future<void> _saveProfile(UserProfile updatedProfile) async { | ||
| final InternetConnectionBloc internetConnectionBloc = | ||
|
|
@@ -72,6 +116,12 @@ class ProfileScreenState extends State<ProfileScreen> { | |
| ); | ||
| } | ||
| } | ||
| 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); | ||
| } | ||
|
Comment on lines
+119
to
+124
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
|
|
||
| @override | ||
|
|
@@ -120,6 +170,19 @@ class ProfileScreenState extends State<ProfileScreen> { | |
|
|
||
| return Column( | ||
| children: <Widget>[ | ||
| GestureDetector( | ||
| onTap: _pickImage, | ||
| child: CircleAvatar( | ||
| radius: 50, | ||
| backgroundImage: _profileImage != null | ||
| ? FileImage(_profileImage!) | ||
| : null, | ||
| child: _profileImage == null | ||
| ? const Icon(Icons.camera_alt, size: 50) | ||
| : null, | ||
| ), | ||
| ), | ||
| const SizedBox(height: 24), | ||
| ElevatedButton( | ||
| onPressed: () { | ||
| Navigator.pushNamed(context, '/support'); | ||
|
|
@@ -154,8 +217,7 @@ class ProfileScreenState extends State<ProfileScreen> { | |
| Center( | ||
| child: ElevatedButton( | ||
| onPressed: () async { | ||
| final String? userId = | ||
| FirebaseAuth.instance.currentUser?.uid; | ||
| final String? userId = FirebaseAuth.instance.currentUser?.uid; | ||
| if (userId != null) { | ||
| final UserProfile updatedProfile = UserProfile( | ||
| userId: userId, | ||
|
|
@@ -164,18 +226,16 @@ class ProfileScreenState extends State<ProfileScreen> { | |
| citizenId: _citizenIdController.text, | ||
| email: _emailController.text, | ||
| phone: _phoneController.text, | ||
| birthDate: DateFormat('MM/dd/yyyy') | ||
| .parse(_birthDateController.text), | ||
| favoriteCuisine: | ||
| _favoriteCuisine ?? CuisineType.other, | ||
| birthDate: DateFormat('MM/dd/yyyy').parse(_birthDateController.text), | ||
| favoriteCuisine: _favoriteCuisine ?? CuisineType.other, | ||
| dietType: _dietType ?? DietType.none, | ||
| ); | ||
|
|
||
| final InternetConnectionBloc internetConnectionBloc = | ||
| context.read<InternetConnectionBloc>(); | ||
| final InternetConnectionBloc internetConnectionBloc = context.read<InternetConnectionBloc>(); | ||
| if (internetConnectionBloc.state is DisconnectedInternet) { | ||
| final SharedPreferences prefs = await SharedPreferences.getInstance(); | ||
| await prefs.setString('cachedProfile', updatedProfile.toMap().toString()); | ||
| final Directory cacheDir = await getTemporaryDirectory(); | ||
| final String profilePath = '${cacheDir.path}/profile.json'; | ||
| await File(profilePath).writeAsString(updatedProfile.toMap().toString()); | ||
| if (mounted) { | ||
| // ignore: use_build_context_synchronously | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
|
|
@@ -188,11 +248,11 @@ class ProfileScreenState extends State<ProfileScreen> { | |
| } | ||
| } else { | ||
| context.read<ProfileBloc>().add( | ||
| UpdateProfileEvent( | ||
| userId: userId, | ||
| updatedProfile: updatedProfile, | ||
| ), | ||
| ); | ||
| UpdateProfileEvent( | ||
| userId: userId, | ||
| updatedProfile: updatedProfile, | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| }, | ||
|
|
||
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.
💡 Codebase verification
Storage permissions need to be updated for modern Android compatibility
The verification reveals several issues that need to be addressed:
READ_MEDIA_IMAGESpermission, causing redundancyandroid:requestLegacyExternalStorage="true"which forces the legacy storage modelprofile_screen.dartstill uses the deprecatedPermission.storageAPIRecommendations:
READ_EXTERNAL_STORAGEandWRITE_EXTERNAL_STORAGEfrom profile/AndroidManifest.xml as they're redundantandroid:requestLegacyExternalStorage="true"from the application tagprofile_screen.dartto usePermission.photosinstead ofPermission.storage🔗 Analysis chain
Consider using scoped storage and modern permission model
While the permissions are correctly declared, consider the following security recommendations:
READ_MEDIA_IMAGESinstead of broad storage permissionsLet's verify the target SDK version and storage implementation:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 183
Script:
Length of output: 3996