diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a29f813..1037ebf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -28,11 +28,6 @@ - - changeImage() async { - final result = await FilePicker.platform.pickFiles( - allowMultiple: false, - type: FileType.image, + Future changeImage(BuildContext context) async { + final picker = ImagePicker(); + final result = await picker.pickImage(source: ImageSource.gallery); + final bytes = await result?.readAsBytes(); + + if (result == null || bytes == null || !context.mounted) { + return; + } + + final controller = CropController(); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CropView( + bytes: bytes, + controller: controller, + ), + ), ); + } - if (result != null) { - final path = result.files.single.path!; - - final croppedFile = await ImageCropper().cropImage( - sourcePath: path, - uiSettings: [ - AndroidUiSettings( - cropStyle: CropStyle.circle, - initAspectRatio: CropAspectRatioPreset.square, - ), - IOSUiSettings( - cropStyle: CropStyle.circle, - aspectRatioPresets: [CropAspectRatioPreset.square], - ), - ], - ); - - final response = await HttpRequest().postMultipart( - '/v1/members/image', - member!.token, - croppedFile!.path, - ); - - if (response.statusCode != 200) { - throw HttpException('Failed to change image ${response.body}'); - } + Future increaseImageVersion() async { + imageVersion++; + await _sharedPreferences.setInt('imageVersion', imageVersion); - imageVersion++; - await _sharedPreferences.setInt('imageVersion', imageVersion); - Future.delayed(const Duration(seconds: 1), () { - streamController.add(member!); - }); - } + Future.delayed(const Duration(seconds: 1), () { + streamController.add(member!); + }); } Future associateEmail(String email) async { diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index eb7d3e6..361d1a3 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -68,5 +68,8 @@ "animesAdded": "Animés ajoutés", "episodesWatched": "Épisodes vus", - "watchTime": "Temps de visionnage" + "watchTime": "Temps de visionnage", + "accountCreatedAt": "Compte créé le {date}", + "crop": "Recadrer", + "next": "Suivant" } diff --git a/lib/utils/http_request.dart b/lib/utils/http_request.dart index 23ff95c..487367b 100644 --- a/lib/utils/http_request.dart +++ b/lib/utils/http_request.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:application/dtos/pageable_dto.dart'; import 'package:application/utils/constant.dart'; +import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; class HttpRequest { @@ -65,7 +66,7 @@ class HttpRequest { Future postMultipart( String endpoint, String token, - String path, + Uint8List bytes, ) async { final request = http.MultipartRequest( 'POST', @@ -76,10 +77,10 @@ class HttpRequest { request.headers.putIfAbsent('Authorization', () => 'Bearer $token'); request.files.add( - await http.MultipartFile.fromPath( + http.MultipartFile.fromBytes( 'file', - path, - filename: path.split('/').last, + bytes, + filename: 'image.jpg', ), ); diff --git a/lib/views/account_view.dart b/lib/views/account_view.dart index d1a6969..201e8f1 100644 --- a/lib/views/account_view.dart +++ b/lib/views/account_view.dart @@ -38,7 +38,7 @@ class AccountView extends StatelessWidget { right: 0, child: GestureDetector( onTap: () { - MemberController.instance.changeImage(); + MemberController.instance.changeImage(context); }, child: Container( decoration: const BoxDecoration( @@ -170,7 +170,8 @@ class AccountView extends StatelessWidget { ), const SizedBox(height: 32), Text( - 'Compte créé le ${beautifyDate(context, member?.creationDateTime ?? '')}', + appLocalizations.accountCreatedAt( + beautifyDate(context, member?.creationDateTime ?? '')), style: const TextStyle( fontSize: 14, ), diff --git a/lib/views/crop_view.dart b/lib/views/crop_view.dart new file mode 100644 index 0000000..16764cd --- /dev/null +++ b/lib/views/crop_view.dart @@ -0,0 +1,61 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:application/controllers/member_controller.dart'; +import 'package:application/utils/http_request.dart'; +import 'package:crop_your_image/crop_your_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class CropView extends StatelessWidget { + final Uint8List bytes; + final CropController controller; + + const CropView({ + super.key, + required this.bytes, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.crop), + ), + body: Column( + children: [ + Expanded( + child: Crop( + controller: controller, + image: bytes, + withCircleUi: true, + baseColor: Colors.black, + onCropped: (value) async { + Navigator.of(context).pop(); + + final response = await HttpRequest().postMultipart( + '/v1/members/image', + MemberController.instance.member!.token, + value, + ); + + if (response.statusCode != 200) { + throw HttpException( + 'Failed to change image ${response.body}'); + } + + MemberController.instance.increaseImageVersion(); + }, + ), + ), + // Rotate button + ElevatedButton( + onPressed: controller.crop, + child: Text(AppLocalizations.of(context)!.next), + ), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 1980a3a..747cec9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -217,6 +217,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + crop_your_image: + dependency: "direct main" + description: + name: crop_your_image + sha256: "9ae3b33042de5bda5321fc48aad41054c196bf2cc28350cd30cb8a85c1a7b1bd" + url: "https://pub.dev" + source: hosted + version: "1.1.0" cross_file: dependency: transitive description: @@ -297,14 +305,38 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.4" - file_picker: - dependency: "direct main" + 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_picker - sha256: "2ca051989f69d1b2ca012b2cf3ccf78c70d40144f0861ff2c063493f7c8c3d45" + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "8.0.5" + 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: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" firebase_core: dependency: "direct main" description: @@ -433,10 +465,10 @@ packages: dependency: "direct main" description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: f54946fdb1fa7b01f780841937b1a80783a20b393485f3f6cdf336fd6f4705f2 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" frontend_server_client: dependency: transitive description: @@ -501,30 +533,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.0" - image_cropper: + image_picker: dependency: "direct main" description: - name: image_cropper - sha256: ee0485e232ca7e41f9397526bd911feb1996440e1e14e3d5f5240664fa435936 + 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: "4161e1f843d8480d2e9025ee22411778c3c9eb7e40076dcf2da23d8242b7b51c" + url: "https://pub.dev" + source: hosted + version: "0.8.12+3" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3" + url: "https://pub.dev" + source: hosted + version: "3.0.4" + 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: "7.0.5" - image_cropper_for_web: + version: "0.2.1+1" + image_picker_platform_interface: dependency: transitive description: - name: image_cropper_for_web - sha256: ae3a307f970d1d2c286bce4519f8cb20c3f0a5332867a912366e1843e24bc8d3 + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" url: "https://pub.dev" source: hosted - version: "5.0.4" - image_cropper_platform_interface: + version: "2.10.0" + image_picker_windows: dependency: transitive description: - name: image_cropper_platform_interface - sha256: "04d0eebd5f901a4109f486d211c888f1fb25c6b70704a405fb1505cb742b05f0" + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "0.2.1+1" intl: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8a32b0f..093104d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,16 +10,16 @@ environment: dependencies: cached_network_image: ^3.3.1 - file_picker: ^8.0.5 + crop_your_image: ^1.1.0 firebase_core: ^3.1.1 firebase_messaging: ^15.0.2 flutter: sdk: flutter flutter_localizations: sdk: flutter - freezed_annotation: ^2.4.1 + freezed_annotation: ^2.4.2 http: ^1.2.1 - image_cropper: ^7.0.5 + image_picker: ^1.1.2 intl: any json_annotation: ^4.9.0 like_button: ^2.0.5