Compare commits

...

2 Commits

Author SHA1 Message Date
Frédérik Benoist 1ce0d03198 feat: image rotation (suite) 2023-03-30 08:50:16 +02:00
Frédérik Benoist d063256dce feat: image rotation 2023-03-30 08:49:14 +02:00
9 changed files with 224 additions and 89 deletions

View File

@ -14,6 +14,7 @@ class Photo {
int id_visite; int id_visite;
int id_photo_typologie; int id_photo_typologie;
String image; String image;
String image_name;
DateTime date_photo; DateTime date_photo;
int id_photo_mp4; int id_photo_mp4;
int photo_privee; int photo_privee;
@ -29,6 +30,7 @@ class Photo {
required this.id_visite, required this.id_visite,
required this.id_photo_typologie, required this.id_photo_typologie,
required this.image, required this.image,
required this.image_name,
this.id_photo = 0, this.id_photo = 0,
this.id_photo_mp4 = 0, this.id_photo_mp4 = 0,
this.photo_privee = 0, this.photo_privee = 0,

View File

@ -1,6 +1,7 @@
// ignore_for_file: prefer_const_constructors // ignore_for_file: prefer_const_constructors
import 'dart:ui'; import 'dart:ui';
import 'dart:io';
import 'package:mobdr/config/constant.dart'; import 'package:mobdr/config/constant.dart';
@ -16,6 +17,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:path_provider/path_provider.dart';
import 'objectbox.dart'; import 'objectbox.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
@ -59,6 +61,8 @@ class MyApp extends StatelessWidget {
// This widget is the root of your application. // This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
initDirectories();
// Initialize all bloc provider used on this entire application here // Initialize all bloc provider used on this entire application here
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
@ -117,4 +121,23 @@ class MyApp extends StatelessWidget {
), ),
); );
} }
void initDirectories() async {
// Get the application's document directory
final Directory documentsDir = await getApplicationDocumentsDirectory();
// Create a Directory object for the "photos" directory in the documents directory
final Directory photosDir = Directory('${documentsDir.path}/photos');
// Check if the "photos" directory exists
if (await photosDir.exists()) {
print(
'The "photos" directory already exists in the documents directory.');
} else {
// Create the "photos" directory if it does not exist
await photosDir.create();
print(
'The "photos" directory has been created in the documents directory.');
}
}
} }

View File

@ -1,12 +1,12 @@
class PhotoModel { class PhotoModel {
late int id;
late int id_visite; late int id_visite;
late int id_photo_typologie; late int id_photo_typologie;
late String image; late String image;
late String image_name;
PhotoModel( PhotoModel(
{required this.id, {required this.id_visite,
required this.id_visite,
required this.id_photo_typologie, required this.id_photo_typologie,
required this.image}); required this.image,
required this.image_name});
} }

View File

@ -283,7 +283,7 @@
}, },
{ {
"id": "9:6788844671665652158", "id": "9:6788844671665652158",
"lastPropertyId": "15:1865824860595482227", "lastPropertyId": "16:539065583624712715",
"name": "Photo", "name": "Photo",
"properties": [ "properties": [
{ {
@ -356,6 +356,11 @@
"id": "15:1865824860595482227", "id": "15:1865824860595482227",
"name": "image", "name": "image",
"type": 9 "type": 9
},
{
"id": "16:539065583624712715",
"name": "image_name",
"type": 9
} }
], ],
"relations": [] "relations": []

View File

@ -68,7 +68,7 @@ class ObjectBox {
concurrentBox.removeAll(); concurrentBox.removeAll();
visiteBox.removeAll(); visiteBox.removeAll();
visiteTagBox.removeAll(); visiteTagBox.removeAll();
photoBox.removeAll(); //photoBox.removeAll();
//photoTypologyBox.removeAll(); //photoTypologyBox.removeAll();
// Add some demo data if the box is empty. // Add some demo data if the box is empty.
@ -343,17 +343,19 @@ class ObjectBox {
/// PHOTO -------------------------------------------------------------------- /// PHOTO --------------------------------------------------------------------
/// ///
void addPhotos(List<Photo> _listPhotos) { void addPhotos(List<Photo> _listPhotos) {
store.box<Photo>().putMany(_listPhotos); store.box<Photo>().putManyAsync(_listPhotos);
} }
Future<void> addPhoto(int id_visite, int id_photo_typologie, String image) => Future<void> addPhoto(int id_visite, int id_photo_typologie, String image,
String image_name) =>
store.runInTransactionAsync( store.runInTransactionAsync(
TxMode.write, TxMode.write,
_addPhotoInTx, _addPhotoInTx,
Photo( Photo(
id_visite: id_visite, id_visite: id_visite,
id_photo_typologie: id_photo_typologie, id_photo_typologie: id_photo_typologie,
image: image)); image: image,
image_name: image_name));
static void _addPhotoInTx(Store store, _Photo) { static void _addPhotoInTx(Store store, _Photo) {
// Perform ObjectBox operations that take longer than a few milliseconds // Perform ObjectBox operations that take longer than a few milliseconds
@ -384,6 +386,15 @@ class ObjectBox {
.map((query) => query.find()); .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) { int getVisitPhotoCount(int _id_visite, int _id_Photo_typologie) {
final builder = photoBox final builder = photoBox
.query(Photo_.id_visite.equals(_id_visite) & .query(Photo_.id_visite.equals(_id_visite) &

View File

@ -308,7 +308,7 @@ final _entities = <ModelEntity>[
ModelEntity( ModelEntity(
id: const IdUid(9, 6788844671665652158), id: const IdUid(9, 6788844671665652158),
name: 'Photo', name: 'Photo',
lastPropertyId: const IdUid(15, 1865824860595482227), lastPropertyId: const IdUid(16, 539065583624712715),
flags: 0, flags: 0,
properties: <ModelProperty>[ properties: <ModelProperty>[
ModelProperty( ModelProperty(
@ -380,6 +380,11 @@ final _entities = <ModelEntity>[
id: const IdUid(15, 1865824860595482227), id: const IdUid(15, 1865824860595482227),
name: 'image', name: 'image',
type: 9, type: 9,
flags: 0),
ModelProperty(
id: const IdUid(16, 539065583624712715),
name: 'image_name',
type: 9,
flags: 0) flags: 0)
], ],
relations: <ModelRelation>[], relations: <ModelRelation>[],
@ -760,7 +765,8 @@ ModelDefinition getObjectBoxModel() {
final photo_tag3Offset = fbb.writeString(object.photo_tag3); final photo_tag3Offset = fbb.writeString(object.photo_tag3);
final photo_tag4Offset = fbb.writeString(object.photo_tag4); final photo_tag4Offset = fbb.writeString(object.photo_tag4);
final imageOffset = fbb.writeString(object.image); 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(0, object.id);
fbb.addInt64(1, object.id_photo); fbb.addInt64(1, object.id_photo);
fbb.addInt64(2, object.id_visite); fbb.addInt64(2, object.id_visite);
@ -775,6 +781,7 @@ ModelDefinition getObjectBoxModel() {
fbb.addOffset(12, photo_tag4Offset); fbb.addOffset(12, photo_tag4Offset);
fbb.addInt64(13, object.uploaded); fbb.addInt64(13, object.uploaded);
fbb.addOffset(14, imageOffset); fbb.addOffset(14, imageOffset);
fbb.addOffset(15, image_nameOffset);
fbb.finish(fbb.endTable()); fbb.finish(fbb.endTable());
return object.id; return object.id;
}, },
@ -790,6 +797,8 @@ ModelDefinition getObjectBoxModel() {
const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0), const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0),
image: const fb.StringReader(asciiOptimization: true) image: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 32, ''), .vTableGet(buffer, rootOffset, 32, ''),
image_name: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 34, ''),
id_photo: id_photo:
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0), const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0),
id_photo_mp4: id_photo_mp4:
@ -802,10 +811,9 @@ ModelDefinition getObjectBoxModel() {
.vTableGet(buffer, rootOffset, 22, ''), .vTableGet(buffer, rootOffset, 22, ''),
photo_tag2: const fb.StringReader(asciiOptimization: true) photo_tag2: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 24, ''), .vTableGet(buffer, rootOffset, 24, ''),
photo_tag3: const fb.StringReader(asciiOptimization: true) photo_tag3:
.vTableGet(buffer, rootOffset, 26, ''), const fb.StringReader(asciiOptimization: true).vTableGet(buffer, rootOffset, 26, ''),
photo_tag4: const fb.StringReader(asciiOptimization: true) photo_tag4: const fb.StringReader(asciiOptimization: true).vTableGet(buffer, rootOffset, 28, ''),
.vTableGet(buffer, rootOffset, 28, ''),
date_photo: DateTime.fromMillisecondsSinceEpoch(const fb.Int64Reader().vTableGet(buffer, rootOffset, 14, 0)), date_photo: DateTime.fromMillisecondsSinceEpoch(const fb.Int64Reader().vTableGet(buffer, rootOffset, 14, 0)),
uploaded: const fb.Int64Reader().vTableGet(buffer, rootOffset, 30, 0)); uploaded: const fb.Int64Reader().vTableGet(buffer, rootOffset, 30, 0));
@ -1072,6 +1080,10 @@ class Photo_ {
/// see [Photo.image] /// see [Photo.image]
static final image = QueryStringProperty<Photo>(_entities[7].properties[13]); static final image = QueryStringProperty<Photo>(_entities[7].properties[13]);
/// see [Photo.image_name]
static final image_name =
QueryStringProperty<Photo>(_entities[7].properties[14]);
} }
/// [PhotoTypology] entity fields to define ObjectBox queries. /// [PhotoTypology] entity fields to define ObjectBox queries.

View File

@ -1,19 +1,32 @@
import 'dart:developer' as developer;
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:mobdr/config/constant.dart';
import 'package:mobdr/main.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:image/image.dart' as img;
import 'package:mobdr/model/photo_model.dart'; import 'package:path/path.dart' as path;
import 'package:mobdr/ui/reusable/cache_image_network.dart'; import 'package:path_provider/path_provider.dart';
import 'package:flutter/painting.dart' show ImageProvider;
import 'package:mobdr/config/constant.dart';
import 'package:mobdr/main.dart';
import 'package:mobdr/ui/reusable/global_widget.dart'; import 'package:mobdr/ui/reusable/global_widget.dart';
import 'package:mobdr/ui/home/photo_camera.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'; import 'package:mobdr/db/box_photo.dart';
// TODO Il faut supprimer les possibles photos du répertoire cache !
extension FileNameExtension on File {
String getFileName() {
String fileName = path.split('/').last;
return fileName;
}
}
class PhotoListPage extends StatefulWidget { class PhotoListPage extends StatefulWidget {
@override @override
_PhotoListPageState createState() => _PhotoListPageState(); _PhotoListPageState createState() => _PhotoListPageState();
@ -46,10 +59,10 @@ class _PhotoListPageState extends State<PhotoListPage> {
void _loadData() { void _loadData() {
for (Photo myPhoto in objectbox.getPhotos2()) { for (Photo myPhoto in objectbox.getPhotos2()) {
_photoData.add(PhotoModel( _photoData.add(PhotoModel(
id: myPhoto.id,
id_visite: myPhoto.id_visite, id_visite: myPhoto.id_visite,
id_photo_typologie: myPhoto.id_photo_typologie, id_photo_typologie: myPhoto.id_photo_typologie,
image: myPhoto.image)); image: myPhoto.image,
image_name: myPhoto.image_name));
} }
} }
@ -89,17 +102,14 @@ class _PhotoListPageState extends State<PhotoListPage> {
}), }),
], ],
), ),
body: RefreshIndicator( body: AnimatedList(
onRefresh: refreshData, key: _listKey,
child: AnimatedList( initialItemCount: _photoData.length,
key: _listKey, physics: AlwaysScrollableScrollPhysics(),
initialItemCount: _photoData.length, itemBuilder: (context, index, animation) {
physics: AlwaysScrollableScrollPhysics(), return _buildPhotolistCard(
itemBuilder: (context, index, animation) { _photoData[index], boxImageSize, animation, index);
return _buildPhotolistCard( },
_photoData[index], boxImageSize, animation, index);
},
),
), ),
floatingActionButton: fabCart(context)); floatingActionButton: fabCart(context));
} }
@ -132,12 +142,8 @@ class _PhotoListPageState extends State<PhotoListPage> {
children: <Widget>[ children: <Widget>[
ClipRRect( ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(10)), borderRadius: BorderRadius.all(Radius.circular(10)),
child: Image.file( child: Image.file(File(photoData.image),
File(photoData.image), fit: BoxFit.cover, height: 100, width: 150),
fit: BoxFit.cover,
height: 100,
width: 150,
),
), ),
SizedBox( SizedBox(
width: 10, width: 10,
@ -165,10 +171,7 @@ class _PhotoListPageState extends State<PhotoListPage> {
children: [ children: [
Icon(Icons.location_on, Icon(Icons.location_on,
color: SOFT_GREY, size: 12), color: SOFT_GREY, size: 12),
Text( Text(' ' + photoData.image_name,
' ' +
photoData.id_photo_typologie
.toString(),
style: TextStyle( style: TextStyle(
fontSize: 11, color: SOFT_GREY)) fontSize: 11, color: SOFT_GREY))
], ],
@ -210,7 +213,7 @@ class _PhotoListPageState extends State<PhotoListPage> {
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () { onTap: () {
showPopupDeleteFavorite(index, boxImageSize); showPopupDeletePhoto(index, boxImageSize);
}, },
child: Container( child: Container(
padding: EdgeInsets.fromLTRB(5, 0, 5, 0), padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
@ -284,8 +287,14 @@ class _PhotoListPageState extends State<PhotoListPage> {
), ),
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () { onTap: () async {
showPopupDeleteFavorite(index, boxImageSize); await rotateAndReplaceImage(
File(_photoData[index].image), 90);
setState(() {
_listKey = GlobalKey();
});
Fluttertoast.showToast(
msg: 'The image has been rotated');
}, },
child: Container( child: Container(
padding: EdgeInsets.fromLTRB(5, 0, 5, 0), padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
@ -303,8 +312,14 @@ class _PhotoListPageState extends State<PhotoListPage> {
), ),
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () { onTap: () async {
showPopupDeleteFavorite(index, boxImageSize); await rotateAndReplaceImage(
File(_photoData[index].image), -90);
setState(() {
_listKey = GlobalKey();
});
Fluttertoast.showToast(
msg: 'The image has been rotated');
}, },
child: Container( child: Container(
padding: EdgeInsets.fromLTRB(5, 0, 5, 0), padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
@ -331,14 +346,13 @@ class _PhotoListPageState extends State<PhotoListPage> {
Widget fabCart(context) { Widget fabCart(context) {
return FloatingActionButton( return FloatingActionButton(
onPressed: () { onPressed: () {
photoFiles.clear();
Route route = MaterialPageRoute( Route route = MaterialPageRoute(
builder: (context) => CameraPage(photoFiles: photoFiles)); builder: (context) => CameraPage(photoFiles: photoFiles));
Navigator.push(context, route).then((val) { Navigator.push(context, route).then((val) {
/// if the user has validated photos /// if the user has validated photos
if (val == true) { if (val == true) {
savePhotos(); savePhotos();
} else {
deletePhotos();
} }
}); });
}, },
@ -376,35 +390,61 @@ class _PhotoListPageState extends State<PhotoListPage> {
); );
} }
void savePhotos() { Future<bool> evictImage(String imageURL) async {
print("Save PHOTOS ----:" + photoFiles.length.toString()); final NetworkImage provider = NetworkImage(imageURL);
return await provider.evict();
}
void savePhotos() async {
if (photoFiles.length > 0) { if (photoFiles.length > 0) {
final List<Photo> _listPhotos = []; final List<Photo> _listPhotos = [];
final List<PhotoModel> _listPhotosModel = [];
for (var photo in photoFiles) { for (var myTmpPhoto in photoFiles) {
_listPhotos /// move jpg file to photo directory
.add(Photo(id_visite: 0, id_photo_typologie: 0, image: photo.path)); final myPhoto = await moveFileFromTempToPhotosDir(myTmpPhoto);
/// 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); objectbox.addPhotos(_listPhotos);
/// refresh widget PhotoListPage /// insert photo(s) in widget
refreshData(); _photoData.insertAll(0, _listPhotosModel);
/// refresh widget
setState(() {
_listKey = GlobalKey();
});
} }
} }
void deletePhotos() { Future<void> deleteFile(File file) async {
print("DELETE PHOTO ------------"); try {
if (await file.exists()) {
// delete files on local storage await file.delete();
photoFiles.forEach((element) { }
//deleteFile(File(element.path)); } catch (e) {
//element.delete(); // Error in getting access to the file.
}); // todo toast
print(e.toString());
}
} }
void showPopupDeleteFavorite(index, boxImageSize) { void showPopupDeletePhoto(index, boxImageSize) {
// set up the buttons // set up the buttons
Widget cancelButton = TextButton( Widget cancelButton = TextButton(
onPressed: () { onPressed: () {
@ -415,6 +455,13 @@ class _PhotoListPageState extends State<PhotoListPage> {
onPressed: () { onPressed: () {
int removeIndex = index; int removeIndex = index;
var removedItem = _photoData.removeAt(removeIndex); var removedItem = _photoData.removeAt(removeIndex);
// delete file on database
objectbox.delPhoto(removedItem.image_name);
// delete file on local storage
deleteFile(new File(removedItem.image));
// This builder is just so that the animation has something // This builder is just so that the animation has something
// to work with before it disappears from view since the original // to work with before it disappears from view since the original
// has already been deleted. // has already been deleted.
@ -427,7 +474,7 @@ class _PhotoListPageState extends State<PhotoListPage> {
Navigator.pop(context); Navigator.pop(context);
Fluttertoast.showToast( Fluttertoast.showToast(
msg: 'Item has been deleted from your favorite', msg: 'Photo has been deleted from your visit',
toastLength: Toast.LENGTH_SHORT); toastLength: Toast.LENGTH_SHORT);
}, },
child: Text('Yes', style: TextStyle(color: SOFT_BLUE))); child: Text('Yes', style: TextStyle(color: SOFT_BLUE)));
@ -438,10 +485,10 @@ class _PhotoListPageState extends State<PhotoListPage> {
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
title: Text( title: Text(
'Delete Favorite', 'Delete Photo',
style: TextStyle(fontSize: 18), 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)), style: TextStyle(fontSize: 13, color: _color1)),
actions: [ actions: [
cancelButton, cancelButton,
@ -458,28 +505,38 @@ class _PhotoListPageState extends State<PhotoListPage> {
); );
} }
Future<void> deleteFile(File file) async { Future<File> moveFileFromTempToPhotosDir(File tempFile) async {
try { // Get the application's document directory
if (await file.exists()) { final Directory documentsDir = await getApplicationDocumentsDirectory();
await file.delete();
} // Set the new file path with the original file name
} catch (e) { final String newPath =
// Error in getting access to the file. '${documentsDir.path}/photos/${tempFile.path.split('/').last}';
// todo toast
print(e.toString()); // Rename the file to move it to the documents directory
} await tempFile.rename(newPath);
// Create and return a new File object for the new file
return File(newPath);
} }
Future refreshData() async { Future<void> rotateAndReplaceImage(File imageFile, int angle) async {
/// clear all data // Read the image file into a Uint8List
/// TODO on pourrait simplement ajouter les nouvelles photos (bien mieux) Uint8List bytes = await imageFile.readAsBytes();
_photoData.clear();
photoFiles.clear();
/// reload all photo data from database // Decode the bytes into an Image object using the image package
_loadData(); img.Image? image = img.decodeImage(bytes);
/// reinitialiez AnimatedList widget // Rotate the image clockwise by 90 degrees
setState(() => _listKey = GlobalKey()); img.Image? rotatedImage = img.copyRotate(image!, angle: angle);
// Encode the rotated image back into a Uint8List
Uint8List rotatedBytes = img.encodePng(rotatedImage);
// Overwrite the original file with the rotated image
await imageFile.writeAsBytes(rotatedBytes);
// remove the flutter cache image
FileImage(imageFile).evict();
} }
} }

View File

@ -17,6 +17,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.6.0" version: "5.6.0"
archive:
dependency: transitive
description:
name: archive
sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d
url: "https://pub.dev"
source: hosted
version: "3.3.6"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -461,6 +469,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" 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: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -781,6 +797,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c
url: "https://pub.dev"
source: hosted
version: "3.7.2"
pool: pool:
dependency: transitive dependency: transitive
description: description:

View File

@ -53,6 +53,7 @@ dependencies:
image_picker: ^0.8.7 image_picker: ^0.8.7
camera: ^0.10.3+2 camera: ^0.10.3+2
permission_handler: 10.2.0 permission_handler: 10.2.0
image: ^4.0.15
flutter_cache_manager: ^3.3.0 flutter_cache_manager: ^3.3.0