From d063256dcef1d0581259b000a562cc39f886ed1e Mon Sep 17 00:00:00 2001 From: Frederik Benoist Date: Thu, 30 Mar 2023 08:49:14 +0200 Subject: [PATCH] feat: image rotation --- .../components/custom_camera_preview.dart | 12 ++ lib/db/box_photo.dart | 2 + lib/model/photo_model.dart | 8 +- lib/objectbox-model.json | 7 +- lib/objectbox.dart | 17 +- lib/objectbox.g.dart | 24 ++- lib/ui/home/photo_list.dart | 163 +++++++++++------- pubspec.lock | 24 +++ pubspec.yaml | 1 + 9 files changed, 177 insertions(+), 81 deletions(-) diff --git a/lib/core/components/custom_camera_preview.dart b/lib/core/components/custom_camera_preview.dart index d0a4083..01354d9 100644 --- a/lib/core/components/custom_camera_preview.dart +++ b/lib/core/components/custom_camera_preview.dart @@ -81,6 +81,8 @@ class _CustomCameraPreviewState extends State { shape: BoxShape.circle), child: GestureDetector( onTap: () { + /// TODO FBE delete image on local storage + fileDelete((widget.photoFiles[index])); setState(() { widget.photoFiles.removeAt(index); }); @@ -101,6 +103,16 @@ class _CustomCameraPreviewState extends State { ); } + Future fileDelete(File file) async { + try { + if (await file.exists()) { + await file.delete(); + } + } catch (e) { + print(e); + } + } + Positioned _rejectButton() { return Positioned( bottom: 5, diff --git a/lib/db/box_photo.dart b/lib/db/box_photo.dart index 18c6df5..0ab3a82 100644 --- a/lib/db/box_photo.dart +++ b/lib/db/box_photo.dart @@ -14,6 +14,7 @@ class Photo { int id_visite; int id_photo_typologie; String image; + String image_name; DateTime date_photo; int id_photo_mp4; int photo_privee; @@ -29,6 +30,7 @@ class Photo { required this.id_visite, required this.id_photo_typologie, required this.image, + required this.image_name, this.id_photo = 0, this.id_photo_mp4 = 0, this.photo_privee = 0, diff --git a/lib/model/photo_model.dart b/lib/model/photo_model.dart index c8c1f78..4fd477e 100644 --- a/lib/model/photo_model.dart +++ b/lib/model/photo_model.dart @@ -1,12 +1,12 @@ class PhotoModel { - late int id; late int id_visite; late int id_photo_typologie; late String image; + late String image_name; PhotoModel( - {required this.id, - required this.id_visite, + {required this.id_visite, required this.id_photo_typologie, - required this.image}); + required this.image, + required this.image_name}); } diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json index 5716c51..779ca10 100644 --- a/lib/objectbox-model.json +++ b/lib/objectbox-model.json @@ -283,7 +283,7 @@ }, { "id": "9:6788844671665652158", - "lastPropertyId": "15:1865824860595482227", + "lastPropertyId": "16:539065583624712715", "name": "Photo", "properties": [ { @@ -356,6 +356,11 @@ "id": "15:1865824860595482227", "name": "image", "type": 9 + }, + { + "id": "16:539065583624712715", + "name": "image_name", + "type": 9 } ], "relations": [] diff --git a/lib/objectbox.dart b/lib/objectbox.dart index 8e51c0b..89c535a 100644 --- a/lib/objectbox.dart +++ b/lib/objectbox.dart @@ -343,17 +343,19 @@ class ObjectBox { /// PHOTO -------------------------------------------------------------------- /// void addPhotos(List _listPhotos) { - store.box().putMany(_listPhotos); + store.box().putManyAsync(_listPhotos); } - Future addPhoto(int id_visite, int id_photo_typologie, String image) => + Future addPhoto(int id_visite, int id_photo_typologie, String image, + String image_name) => store.runInTransactionAsync( TxMode.write, _addPhotoInTx, Photo( id_visite: id_visite, id_photo_typologie: id_photo_typologie, - image: image)); + image: image, + image_name: image_name)); static void _addPhotoInTx(Store store, _Photo) { // Perform ObjectBox operations that take longer than a few milliseconds @@ -384,6 +386,15 @@ class ObjectBox { .map((query) => query.find()); } + void delPhoto(String _name) { + final query = photoBox.query(Photo_.image_name.equals(_name)).build(); + final results = query.find(); + if (results.isNotEmpty) { + final photoToDelete = results.first; + photoBox.removeAsync(photoToDelete.id); + } + } + int getVisitPhotoCount(int _id_visite, int _id_Photo_typologie) { final builder = photoBox .query(Photo_.id_visite.equals(_id_visite) & diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart index ab8503b..8f775b0 100644 --- a/lib/objectbox.g.dart +++ b/lib/objectbox.g.dart @@ -308,7 +308,7 @@ final _entities = [ ModelEntity( id: const IdUid(9, 6788844671665652158), name: 'Photo', - lastPropertyId: const IdUid(15, 1865824860595482227), + lastPropertyId: const IdUid(16, 539065583624712715), flags: 0, properties: [ ModelProperty( @@ -380,6 +380,11 @@ final _entities = [ id: const IdUid(15, 1865824860595482227), name: 'image', type: 9, + flags: 0), + ModelProperty( + id: const IdUid(16, 539065583624712715), + name: 'image_name', + type: 9, flags: 0) ], relations: [], @@ -760,7 +765,8 @@ ModelDefinition getObjectBoxModel() { final photo_tag3Offset = fbb.writeString(object.photo_tag3); final photo_tag4Offset = fbb.writeString(object.photo_tag4); final imageOffset = fbb.writeString(object.image); - fbb.startTable(16); + final image_nameOffset = fbb.writeString(object.image_name); + fbb.startTable(17); fbb.addInt64(0, object.id); fbb.addInt64(1, object.id_photo); fbb.addInt64(2, object.id_visite); @@ -775,6 +781,7 @@ ModelDefinition getObjectBoxModel() { fbb.addOffset(12, photo_tag4Offset); fbb.addInt64(13, object.uploaded); fbb.addOffset(14, imageOffset); + fbb.addOffset(15, image_nameOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -790,6 +797,8 @@ ModelDefinition getObjectBoxModel() { const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0), image: const fb.StringReader(asciiOptimization: true) .vTableGet(buffer, rootOffset, 32, ''), + image_name: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 34, ''), id_photo: const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0), id_photo_mp4: @@ -802,10 +811,9 @@ ModelDefinition getObjectBoxModel() { .vTableGet(buffer, rootOffset, 22, ''), photo_tag2: const fb.StringReader(asciiOptimization: true) .vTableGet(buffer, rootOffset, 24, ''), - photo_tag3: const fb.StringReader(asciiOptimization: true) - .vTableGet(buffer, rootOffset, 26, ''), - photo_tag4: const fb.StringReader(asciiOptimization: true) - .vTableGet(buffer, rootOffset, 28, ''), + photo_tag3: + const fb.StringReader(asciiOptimization: true).vTableGet(buffer, rootOffset, 26, ''), + photo_tag4: const fb.StringReader(asciiOptimization: true).vTableGet(buffer, rootOffset, 28, ''), date_photo: DateTime.fromMillisecondsSinceEpoch(const fb.Int64Reader().vTableGet(buffer, rootOffset, 14, 0)), uploaded: const fb.Int64Reader().vTableGet(buffer, rootOffset, 30, 0)); @@ -1072,6 +1080,10 @@ class Photo_ { /// see [Photo.image] static final image = QueryStringProperty(_entities[7].properties[13]); + + /// see [Photo.image_name] + static final image_name = + QueryStringProperty(_entities[7].properties[14]); } /// [PhotoTypology] entity fields to define ObjectBox queries. diff --git a/lib/ui/home/photo_list.dart b/lib/ui/home/photo_list.dart index dc63e37..2460675 100644 --- a/lib/ui/home/photo_list.dart +++ b/lib/ui/home/photo_list.dart @@ -1,19 +1,28 @@ +import 'dart:developer' as developer; import 'dart:async'; import 'dart:io'; -import 'package:mobdr/config/constant.dart'; -import 'package:mobdr/main.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; + import 'package:fluttertoast/fluttertoast.dart'; -import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -import 'package:mobdr/model/photo_model.dart'; -import 'package:mobdr/ui/reusable/cache_image_network.dart'; +import 'package:image/image.dart' as img; +import 'package:path/path.dart' as path; + +import 'package:mobdr/config/constant.dart'; +import 'package:mobdr/main.dart'; import 'package:mobdr/ui/reusable/global_widget.dart'; import 'package:mobdr/ui/home/photo_camera.dart'; -import 'package:mobdr/objectbox.dart'; +import 'package:mobdr/model/photo_model.dart'; import 'package:mobdr/db/box_photo.dart'; +extension FileNameExtension on File { + String getFileName() { + String fileName = path.split('/').last; + return fileName; + } +} + class PhotoListPage extends StatefulWidget { @override _PhotoListPageState createState() => _PhotoListPageState(); @@ -46,10 +55,10 @@ class _PhotoListPageState extends State { void _loadData() { for (Photo myPhoto in objectbox.getPhotos2()) { _photoData.add(PhotoModel( - id: myPhoto.id, id_visite: myPhoto.id_visite, id_photo_typologie: myPhoto.id_photo_typologie, - image: myPhoto.image)); + image: myPhoto.image, + image_name: myPhoto.image_name)); } } @@ -89,17 +98,14 @@ class _PhotoListPageState extends State { }), ], ), - body: RefreshIndicator( - onRefresh: refreshData, - child: AnimatedList( - key: _listKey, - initialItemCount: _photoData.length, - physics: AlwaysScrollableScrollPhysics(), - itemBuilder: (context, index, animation) { - return _buildPhotolistCard( - _photoData[index], boxImageSize, animation, index); - }, - ), + body: AnimatedList( + key: _listKey, + initialItemCount: _photoData.length, + physics: AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index, animation) { + return _buildPhotolistCard( + _photoData[index], boxImageSize, animation, index); + }, ), floatingActionButton: fabCart(context)); } @@ -165,10 +171,7 @@ class _PhotoListPageState extends State { children: [ Icon(Icons.location_on, color: SOFT_GREY, size: 12), - Text( - ' ' + - photoData.id_photo_typologie - .toString(), + Text(' ' + photoData.image_name, style: TextStyle( fontSize: 11, color: SOFT_GREY)) ], @@ -210,7 +213,7 @@ class _PhotoListPageState extends State { GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { - showPopupDeleteFavorite(index, boxImageSize); + showPopupDeletePhoto(index, boxImageSize); }, child: Container( padding: EdgeInsets.fromLTRB(5, 0, 5, 0), @@ -284,8 +287,12 @@ class _PhotoListPageState extends State { ), GestureDetector( behavior: HitTestBehavior.translucent, - onTap: () { - showPopupDeleteFavorite(index, boxImageSize); + onTap: () async { + await rotateAndReplaceImage( + File(_photoData[index].image)); + setState(() => _listKey = GlobalKey()); + Fluttertoast.showToast( + msg: 'The image has been rotated'); }, child: Container( padding: EdgeInsets.fromLTRB(5, 0, 5, 0), @@ -304,7 +311,7 @@ class _PhotoListPageState extends State { GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { - showPopupDeleteFavorite(index, boxImageSize); + showPopupDeletePhoto(index, boxImageSize); }, child: Container( padding: EdgeInsets.fromLTRB(5, 0, 5, 0), @@ -331,6 +338,7 @@ class _PhotoListPageState extends State { Widget fabCart(context) { return FloatingActionButton( onPressed: () { + photoFiles.clear(); Route route = MaterialPageRoute( builder: (context) => CameraPage(photoFiles: photoFiles)); Navigator.push(context, route).then((val) { @@ -338,7 +346,7 @@ class _PhotoListPageState extends State { if (val == true) { savePhotos(); } else { - deletePhotos(); + resetPhotos(); } }); }, @@ -377,34 +385,58 @@ class _PhotoListPageState extends State { } void savePhotos() { - print("Save PHOTOS ----:" + photoFiles.length.toString()); - if (photoFiles.length > 0) { final List _listPhotos = []; + final List _listPhotosModel = []; - for (var photo in photoFiles) { - _listPhotos - .add(Photo(id_visite: 0, id_photo_typologie: 0, image: photo.path)); + for (var myPhoto in photoFiles) { + /// database + _listPhotos.add(Photo( + id_visite: 0, + id_photo_typologie: 0, + image: myPhoto.path, + image_name: myPhoto.path.split('/').last)); + + /// widget + _listPhotosModel.add(PhotoModel( + id_visite: 0, + id_photo_typologie: 0, + image: myPhoto.path, + image_name: myPhoto.path.split('/').last)); } + /// insert photo(s) in database (async) objectbox.addPhotos(_listPhotos); - /// refresh widget PhotoListPage - refreshData(); + /// insert photo(s) in widget + _photoData.insertAll(0, _listPhotosModel); + + /// refresh widget + setState(() => _listKey = GlobalKey()); } } - void deletePhotos() { - print("DELETE PHOTO ------------"); - - // delete files on local storage - photoFiles.forEach((element) { - //deleteFile(File(element.path)); - //element.delete(); - }); + void resetPhotos() { + /// for all photos taken + for (var photo in photoFiles) { + /// delete image on local storage + deleteFile(photo); + } } - void showPopupDeleteFavorite(index, boxImageSize) { + Future deleteFile(File file) async { + try { + if (await file.exists()) { + await file.delete(); + } + } catch (e) { + // Error in getting access to the file. + // todo toast + print(e.toString()); + } + } + + void showPopupDeletePhoto(index, boxImageSize) { // set up the buttons Widget cancelButton = TextButton( onPressed: () { @@ -415,6 +447,10 @@ class _PhotoListPageState extends State { onPressed: () { int removeIndex = index; var removedItem = _photoData.removeAt(removeIndex); + // delete file on database + objectbox.delPhoto(removedItem.image_name); + // delete file on locale storages + deleteFile(new File(removedItem.image)); // This builder is just so that the animation has something // to work with before it disappears from view since the original // has already been deleted. @@ -427,7 +463,7 @@ class _PhotoListPageState extends State { Navigator.pop(context); Fluttertoast.showToast( - msg: 'Item has been deleted from your favorite', + msg: 'Photo has been deleted from your visit', toastLength: Toast.LENGTH_SHORT); }, child: Text('Yes', style: TextStyle(color: SOFT_BLUE))); @@ -438,10 +474,10 @@ class _PhotoListPageState extends State { borderRadius: BorderRadius.circular(10), ), title: Text( - 'Delete Favorite', + 'Delete Photo', style: TextStyle(fontSize: 18), ), - content: Text('Are you sure to delete this item from your Favorite ?', + content: Text('Are you sure to delete this photo from your visit ?', style: TextStyle(fontSize: 13, color: _color1)), actions: [ cancelButton, @@ -458,28 +494,21 @@ class _PhotoListPageState extends State { ); } - Future deleteFile(File file) async { - try { - if (await file.exists()) { - await file.delete(); - } - } catch (e) { - // Error in getting access to the file. - // todo toast - print(e.toString()); - } - } + Future rotateAndReplaceImage(File imageFile) async { + // Read the image file into a Uint8List + Uint8List bytes = await imageFile.readAsBytes(); - Future refreshData() async { - /// clear all data - /// TODO on pourrait simplement ajouter les nouvelles photos (bien mieux) - _photoData.clear(); - photoFiles.clear(); + // Decode the bytes into an Image object using the image package + img.Image? image = img.decodeImage(bytes); - /// reload all photo data from database - _loadData(); + // Rotate the image clockwise by 90 degrees + img.Image? rotatedImage = img.copyRotate(image!, angle: 10); - /// reinitialiez AnimatedList widget - setState(() => _listKey = GlobalKey()); + // Encode the rotated image back into a Uint8List + Uint8List rotatedBytes = img.encodePng(rotatedImage); + + // Overwrite the original file with the rotated image + deleteFile(imageFile); + await imageFile.writeAsBytes(rotatedBytes, flush: true); } } diff --git a/pubspec.lock b/pubspec.lock index 7bccca2..cb9258c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.6.0" + archive: + dependency: transitive + description: + name: archive + sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + url: "https://pub.dev" + source: hosted + version: "3.3.6" args: dependency: transitive description: @@ -461,6 +469,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: "direct main" + description: + name: image + sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227" + url: "https://pub.dev" + source: hosted + version: "4.0.15" image_picker: dependency: "direct main" description: @@ -781,6 +797,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c + url: "https://pub.dev" + source: hosted + version: "3.7.2" pool: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1229687..53d0476 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: image_picker: ^0.8.7 camera: ^0.10.3+2 permission_handler: 10.2.0 + image: ^4.0.15 flutter_cache_manager: ^3.3.0