diff --git a/API/Application/Users/Commands/UpdateUser.cs b/API/Application/Users/Commands/UpdateUser.cs index e51e0fb..2fb17bb 100644 --- a/API/Application/Users/Commands/UpdateUser.cs +++ b/API/Application/Users/Commands/UpdateUser.cs @@ -60,7 +60,7 @@ namespace API.Application.Users.Commands return new ConflictObjectResult(new { message = "Password is not secure." }); } } - + string imageUrl = null; if (updateUserDTO.ProfilePicture != null && updateUserDTO.ProfilePicture.Length > 0) @@ -69,7 +69,7 @@ namespace API.Application.Users.Commands { using (var fileStream = updateUserDTO.ProfilePicture.OpenReadStream()) { - imageUrl = await _r2Service.UploadToR2(fileStream, "PP" + updateUserDTO.Id+".png"); + imageUrl = await _r2Service.UploadToR2(fileStream, "PP" + updateUserDTO.Id + ".png"); currentUser.ProfilePicture = imageUrl; } } diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index ac0c6d4..ef71ba7 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -60,7 +60,7 @@ namespace API.Controllers return await _loginUser.Handle(login); } - [Authorize] + //[Authorize] [HttpGet] public async Task>> GetUsers() { @@ -75,7 +75,7 @@ namespace API.Controllers [Authorize] [HttpPut] - public async Task PutUser([FromForm] UpdateUserDTO UpdateUserDTO) + public async Task PutUser(UpdateUserDTO UpdateUserDTO) { return await _updateUser.Handle(UpdateUserDTO); } diff --git a/API/Persistence/Services/R2Service.cs b/API/Persistence/Services/R2Service.cs index c8717b4..ff73060 100644 --- a/API/Persistence/Services/R2Service.cs +++ b/API/Persistence/Services/R2Service.cs @@ -29,7 +29,6 @@ namespace API.Persistence.Services var request = new PutObjectRequest { InputStream = fileStream, - BucketName = "h4picturebucket", Key = fileName, DisablePayloadSigning = true }; diff --git a/Mobile/ios/Runner/Info.plist b/Mobile/ios/Runner/Info.plist index ad6afb0..d00c4d0 100644 --- a/Mobile/ios/Runner/Info.plist +++ b/Mobile/ios/Runner/Info.plist @@ -2,7 +2,11 @@ - NSLocationWhenInUseUsageDescription + NSPhotoLibraryUsageDescription + Needs permission for library to be able to upload pictures to reviews and profile picture + NSCameraUsageDescription + Needs permission to the camera to be able to upload pictures to reviews and profile picture + This app needs access to location when open. CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) diff --git a/Mobile/lib/editprofile.dart b/Mobile/lib/editprofile.dart index 0db8d2b..9caf4bb 100644 --- a/Mobile/lib/editprofile.dart +++ b/Mobile/lib/editprofile.dart @@ -1,4 +1,8 @@ + +import 'dart:io'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:mobile/models.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'api.dart' as api; @@ -6,7 +10,7 @@ import 'base/variables.dart'; class EditProfilePage extends StatefulWidget { final User? userData; - + const EditProfilePage({super.key, required this.userData}); @@ -19,7 +23,8 @@ class _ProfilePageState extends State { TextEditingController emailInput = TextEditingController(); TextEditingController passwordInput = TextEditingController(); TextEditingController confirmPasswordInput = TextEditingController(); - + File? _selectedImage; + set userData(User userData) {} @@ -41,6 +46,50 @@ class _ProfilePageState extends State { super.dispose(); } + Future _pickImageFromGallery() async{ + final picker = ImagePicker(); + final XFile? image = await picker.pickImage(source: ImageSource.gallery); + + if(image == null) {return;} + else{ + File compressedFile = await _compressImage(File(image.path)); + setState(() { + _selectedImage = compressedFile; + }); + } + + } + + Future _pickImageFromCamera() async{ + final picker = ImagePicker(); + final XFile? image = await picker.pickImage(source: ImageSource.camera); + + if(image == null) {return;} + else{ + File compressedFile = await _compressImage(File(image.path)); + setState(() { + _selectedImage = compressedFile; + }); + } + } + + Future _compressImage(File file) async { + final filePath = file.absolute.path; + final lastIndex = filePath.lastIndexOf(RegExp(r'.jp')); + final splitted = filePath.substring(0, lastIndex); + final outPath = "${splitted}_compressed.jpg"; + + var result = await FlutterImageCompress.compressAndGetFile( + file.absolute.path, + outPath, + quality: 80, + minWidth: 1024, + minHeight: 1024, + ); + + return File(result!.path); + } + void _saveProfile() async { if (passwordInput.text != confirmPasswordInput.text) { ScaffoldMessenger.of(context).showSnackBar( @@ -54,12 +103,13 @@ class _ProfilePageState extends State { if (!mounted) { return; } - - final response = await api.request(context, api.ApiService.auth, 'PUT', '/api/users', { + if (id != null){ + final response = await api.request(context, api.ApiService.auth, 'PUT', '/api/users', { 'id' : id, 'username': usernameInput.text, 'email': emailInput.text, 'password': passwordInput.text, + 'profilePicture': _selectedImage, }); if (!mounted) { @@ -68,9 +118,10 @@ class _ProfilePageState extends State { if (response != null) { User updatedUser = User( - id!, + id, emailInput.text, usernameInput.text, + _selectedImage, DateTime.now(), ); setState(() { @@ -84,7 +135,8 @@ class _ProfilePageState extends State { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Something went wrong! Please contact an admin.')), ); - } + } + } } void _deleteProfile(BuildContext context) { @@ -167,7 +219,16 @@ class _ProfilePageState extends State { controller: confirmPasswordInput, decoration: InputDecoration(labelText: 'Repeat new password'), ), + Row( + children: [ + ElevatedButton(onPressed: _pickImageFromGallery, child: Text('Gallery')), + ElevatedButton(onPressed: _pickImageFromCamera, child: Text('Camera')) + ], + ), SizedBox(height: 20), + Text('ProfilePicture:'), + if(_selectedImage != null) + Text(_selectedImage!.path.toString()), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart index 8739fd1..d42bdc3 100644 --- a/Mobile/lib/models.dart +++ b/Mobile/lib/models.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:latlong2/latlong.dart'; class Favorite { @@ -41,16 +43,18 @@ class Login { class User { String id; String email; - String username; + String username; + File? profilePicture; DateTime createdAt; - User( this.id, this.email, this.username, this.createdAt); + User( this.id, this.email, this.username, this.profilePicture, this.createdAt); factory User.fromJson(Map json) { return User( json['id'], json['email'], json['username'], + json['profilePicture'], DateTime.parse(json['createdAt']), ); } diff --git a/Mobile/linux/flutter/generated_plugin_registrant.cc b/Mobile/linux/flutter/generated_plugin_registrant.cc index e71a16d..64a0ece 100644 --- a/Mobile/linux/flutter/generated_plugin_registrant.cc +++ b/Mobile/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); } diff --git a/Mobile/linux/flutter/generated_plugins.cmake b/Mobile/linux/flutter/generated_plugins.cmake index 2e1de87..2db3c22 100644 --- a/Mobile/linux/flutter/generated_plugins.cmake +++ b/Mobile/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/Mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/Mobile/macos/Flutter/GeneratedPluginRegistrant.swift index 738f6dd..e491e12 100644 --- a/Mobile/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/Mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,11 +5,15 @@ import FlutterMacOS import Foundation +import file_selector_macos +import flutter_image_compress_macos import geolocator_apple import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/Mobile/pubspec.lock b/Mobile/pubspec.lock index b5d1008..5ffc63c 100644 --- a/Mobile/pubspec.lock +++ b/Mobile/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + 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: @@ -89,6 +97,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.dev" + source: hosted + version: "0.9.4" + 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: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" fixnum: dependency: transitive description: @@ -102,6 +142,54 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_image_compress: + dependency: "direct main" + description: + name: flutter_image_compress + sha256: "45a3071868092a61b11044c70422b04d39d4d9f2ef536f3c5b11fb65a1e7dd90" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + flutter_image_compress_macos: + dependency: transitive + description: + name: flutter_image_compress_macos + sha256: "26df6385512e92b3789dc76b613b54b55c457a7f1532e59078b04bf189782d47" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_image_compress_ohos: + dependency: transitive + description: + name: flutter_image_compress_ohos + sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 + url: "https://pub.dev" + source: hosted + version: "0.0.3" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: f02fe352b17f82b72f481de45add240db062a2585850bea1667e82cc4cd6c311 + url: "https://pub.dev" + source: hosted + version: "0.1.4+1" flutter_lints: dependency: "direct dev" description: @@ -118,6 +206,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.2" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" + url: "https://pub.dev" + source: hosted + version: "2.0.22" flutter_test: dependency: "direct dev" description: flutter @@ -200,6 +296,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: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85 + url: "https://pub.dev" + source: hosted + version: "0.8.12+13" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + url: "https://pub.dev" + source: hosted + version: "0.8.12" + 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" intl: dependency: transitive description: @@ -208,6 +368,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" latlong2: dependency: "direct main" description: @@ -296,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mime: + dependency: transitive + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" path: dependency: transitive description: @@ -566,5 +742,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/Mobile/pubspec.yaml b/Mobile/pubspec.yaml index 21732f9..789f90a 100644 --- a/Mobile/pubspec.yaml +++ b/Mobile/pubspec.yaml @@ -40,6 +40,8 @@ dependencies: shared_preferences: ^2.3.2 google_fonts: ^6.2.1 geolocator: ^13.0.1 + image_picker: ^1.1.2 + flutter_image_compress: ^2.3.0 dev_dependencies: flutter_test: diff --git a/Mobile/windows/flutter/generated_plugin_registrant.cc b/Mobile/windows/flutter/generated_plugin_registrant.cc index 1ece8f2..f35b3a6 100644 --- a/Mobile/windows/flutter/generated_plugin_registrant.cc +++ b/Mobile/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); } diff --git a/Mobile/windows/flutter/generated_plugins.cmake b/Mobile/windows/flutter/generated_plugins.cmake index 7f101a7..389222b 100644 --- a/Mobile/windows/flutter/generated_plugins.cmake +++ b/Mobile/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows geolocator_windows )