diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7832420..056699b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,8 +4,13 @@ + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index 1d14ce6..234b328 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -4,4 +4,7 @@ to allow setting breakpoints, to provide hot reload, etc. --> + + + diff --git a/lib/features/home/presentation/screens/rating_screen.dart b/lib/features/home/presentation/screens/rating_screen.dart index 90d4a68..c4ff025 100644 --- a/lib/features/home/presentation/screens/rating_screen.dart +++ b/lib/features/home/presentation/screens/rating_screen.dart @@ -78,6 +78,7 @@ class _RatingScreenState extends State { } void _submitRating(BuildContext context) { + // ignore: unused_local_variable final String comment = _commentController.text.trim(); // Perform the rating submission logic here diff --git a/lib/features/home/presentation/widgets/for_you_tab.dart b/lib/features/home/presentation/widgets/for_you_tab.dart index 7f8cf7c..48cd04d 100644 --- a/lib/features/home/presentation/widgets/for_you_tab.dart +++ b/lib/features/home/presentation/widgets/for_you_tab.dart @@ -9,12 +9,12 @@ import 'package:eco_bites/features/food/domain/entities/offer.dart'; import 'package:eco_bites/features/food/presentation/bloc/food_business_bloc.dart'; import 'package:eco_bites/features/food/presentation/bloc/food_business_event.dart'; import 'package:eco_bites/features/food/presentation/bloc/food_business_state.dart'; +import 'package:eco_bites/features/home/presentation/screens/rating_screen.dart'; import 'package:eco_bites/features/profile/presentation/bloc/profile_bloc.dart'; import 'package:eco_bites/features/profile/presentation/bloc/profile_state.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:eco_bites/features/home/presentation/screens/rating_screen.dart'; class ForYouTab extends StatelessWidget { const ForYouTab({super.key}); @@ -70,7 +70,7 @@ class ForYouTab extends StatelessWidget { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) => RatingScreen( - restaurantName: foodBusiness.name), + restaurantName: foodBusiness.name,), ), ); }, diff --git a/lib/features/profile/presentation/bloc/profile_bloc.dart b/lib/features/profile/presentation/bloc/profile_bloc.dart index d615ced..14f592c 100644 --- a/lib/features/profile/presentation/bloc/profile_bloc.dart +++ b/lib/features/profile/presentation/bloc/profile_bloc.dart @@ -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 @@ -50,6 +52,13 @@ class ProfileBloc extends Bloc ); 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 + } + emit( ProfileError( 'No internet connection. Your data will be saved when you are online.', diff --git a/lib/features/profile/presentation/screens/profile_screen.dart b/lib/features/profile/presentation/screens/profile_screen.dart index 4477cef..da9bfce 100644 --- a/lib/features/profile/presentation/screens/profile_screen.dart +++ b/lib/features/profile/presentation/screens/profile_screen.dart @@ -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 { 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().add(LoadProfileEvent(userId)); @@ -45,6 +52,43 @@ class ProfileScreenState extends State { } } + Future _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 _loadProfileImage() async { + final Directory cacheDir = await getTemporaryDirectory(); + final String imagePath = '${cacheDir.path}/profile_image.png'; + if (File(imagePath).existsSync()) { + setState(() { + _profileImage = File(imagePath); + }); + } + } + // ignore: unused_element Future _saveProfile(UserProfile updatedProfile) async { final InternetConnectionBloc internetConnectionBloc = @@ -72,6 +116,12 @@ class ProfileScreenState extends State { ); } } + 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); + } } @override @@ -120,6 +170,19 @@ class ProfileScreenState extends State { return Column( children: [ + 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 { 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 { 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(); + final InternetConnectionBloc internetConnectionBloc = context.read(); 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 { } } else { context.read().add( - UpdateProfileEvent( - userId: userId, - updatedProfile: updatedProfile, - ), - ); + UpdateProfileEvent( + userId: userId, + updatedProfile: updatedProfile, + ), + ); } } }, diff --git a/lib/features/support/presentation/bloc/support_bloc.dart b/lib/features/support/presentation/bloc/support_bloc.dart index 83c54b9..525746d 100644 --- a/lib/features/support/presentation/bloc/support_bloc.dart +++ b/lib/features/support/presentation/bloc/support_bloc.dart @@ -30,7 +30,6 @@ class SupportBloc extends Bloc { ) async { emit(SupportLoading()); try { - // Intentar subir el ticket a Firestore await firestore.collection('support_tickets').add({ 'category': event.category, 'subOption': event.subOption, diff --git a/pubspec.lock b/pubspec.lock index 1a0fc4a..aab6544 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + camera: + dependency: "direct main" + description: + name: camera + sha256: "26ff41045772153f222ffffecba711a206f670f5834d40ebf5eed3811692f167" + url: "https://pub.dev" + source: hosted + version: "0.11.0+2" + camera_android_camerax: + dependency: transitive + description: + name: camera_android_camerax + sha256: e3627fdc2132d89212b8a8676679f5b07008c7e3d8ae00cea775c3397f9e742b + url: "https://pub.dev" + source: hosted + version: "0.6.10" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "2e4c568f70e406ccb87376bc06b53d2f5bebaab71e2fbcc1a950e31449381bcf" + url: "https://pub.dev" + source: hosted + version: "0.9.17+5" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: b3ede1f171532e0d83111fe0980b46d17f1aa9788a07a2fbed07366bbdbb9061 + url: "https://pub.dev" + source: hosted + version: "2.8.0" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" + url: "https://pub.dev" + source: hosted + version: "0.3.5" characters: dependency: transitive description: @@ -105,6 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -177,6 +225,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" firebase_analytics: dependency: "direct main" description: @@ -536,6 +616,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: fa8141602fde3f7e2f81dbf043613eb44dfa325fa0bcf93c0f142c9f7a2c193e + url: "https://pub.dev" + source: hosted + version: "0.8.12+18" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" + url: "https://pub.dev" + source: hosted + version: "0.8.12+1" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" internet_connection_checker: dependency: "direct main" description: @@ -624,6 +768,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" nested: dependency: "direct main" description: @@ -649,7 +801,7 @@ packages: source: hosted version: "1.9.0" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" @@ -696,6 +848,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + url: "https://pub.dev" + source: hosted + version: "11.3.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" + url: "https://pub.dev" + source: hosted + version: "12.0.13" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 + url: "https://pub.dev" + source: hosted + version: "9.4.5" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 + url: "https://pub.dev" + source: hosted + version: "4.2.3" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" petitparser: dependency: transitive description: @@ -945,10 +1145,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" web: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d06e8f4..82ae4fc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,7 @@ environment: # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: + camera: ^0.11.0+2 cloud_firestore: ^5.4.3 connectivity_plus: ^6.1.0 cupertino_icons: ^1.0.8 @@ -49,6 +50,7 @@ dependencies: google_maps_flutter: ^2.9.0 google_sign_in: ^6.2.1 http: ^1.2.2 + image_picker: ^1.1.2 internet_connection_checker: ^1.0.0 intl: ^0.19.0 logger: ^2.4.0 @@ -56,6 +58,8 @@ dependencies: meta: ^1.15.0 nested: ^1.0.0 path: ^1.9.0 + path_provider: ^2.1.5 + permission_handler: ^11.3.1 shared_preferences: ^2.3.2 sign_button: ^2.0.6 sqflite: ^2.4.1