import 'dart:developer' as developer; import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.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/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(); } class _PhotoListPageState extends State { final _globalWidget = GlobalWidget(); // initialize photos files list final List photoFiles = []; List _photoData = []; Color _color1 = Color(0xff777777); Color _color2 = Color(0xFF515151); // _listKey is used for AnimatedList var _listKey = GlobalKey(); @override void initState() { super.initState(); _loadData(); } @override void dispose() { super.dispose(); } void _loadData() { for (Photo myPhoto in objectbox.getPhotos2()) { _photoData.add(PhotoModel( id_visite: myPhoto.id_visite, id_photo_typologie: myPhoto.id_photo_typologie, image: myPhoto.image, image_name: myPhoto.image_name)); } } @override Widget build(BuildContext context) { final double boxImageSize = (MediaQuery.of(context).size.width / 4); return Scaffold( appBar: AppBar( iconTheme: IconThemeData( color: Colors.black, //change your color here ), systemOverlayStyle: SystemUiOverlayStyle.dark, elevation: 0, title: Text( 'Catégorie : A trier', style: TextStyle(fontSize: 18, color: Colors.black), ), backgroundColor: Colors.white, actions: [ GestureDetector( onTap: () { Fluttertoast.showToast( msg: 'Click message', toastLength: Toast.LENGTH_SHORT); }, child: Icon(Icons.email, color: _color1)), IconButton( icon: _globalWidget.customNotifIcon( count: 8, notifColor: _color1, notifSize: 24, labelSize: 14), //icon: _globalWidget.customNotifIcon2(8, _color1), onPressed: () { Fluttertoast.showToast( msg: 'Click notification', toastLength: Toast.LENGTH_SHORT); }), ], ), body: AnimatedList( key: _listKey, initialItemCount: _photoData.length, physics: AlwaysScrollableScrollPhysics(), itemBuilder: (context, index, animation) { return _buildPhotolistCard( _photoData[index], boxImageSize, animation, index); }, ), floatingActionButton: fabCart(context)); } Widget _buildPhotolistCard( PhotoModel photoData, boxImageSize, animation, index) { return SizeTransition( sizeFactor: animation, child: Container( margin: EdgeInsets.fromLTRB(12, 6, 12, 0), child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), elevation: 2, color: Colors.white, child: Container( margin: EdgeInsets.all(8), child: Column( children: [ GestureDetector( onTap: () { Fluttertoast.showToast( msg: 'Click ' + photoData.image, toastLength: Toast.LENGTH_SHORT); }, child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.all(Radius.circular(10)), child: Image.file( File(photoData.image), fit: BoxFit.cover, height: 100, width: 150, ), ), SizedBox( width: 10, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( photoData.id_photo_typologie.toString(), style: TextStyle(fontSize: 13, color: _color2), maxLines: 3, overflow: TextOverflow.ellipsis, ), Container( margin: EdgeInsets.only(top: 5), child: Text((index + 1).toString(), style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold)), ), Container( margin: EdgeInsets.only(top: 5), child: Row( children: [ Icon(Icons.location_on, color: SOFT_GREY, size: 12), Text(' ' + photoData.image_name, style: TextStyle( fontSize: 11, color: SOFT_GREY)) ], ), ), Container( margin: EdgeInsets.only(top: 5), child: Row( children: [ Text( '(' + photoData.id_photo_typologie .toString() + ')', style: TextStyle( fontSize: 11, color: SOFT_GREY)) ], ), ), Container( margin: EdgeInsets.only(top: 5), child: Text( photoData.id_photo_typologie.toString() + ' ' + 'Sale', style: TextStyle( fontSize: 11, color: SOFT_GREY)), ), ], ), ) ], ), ), Container( margin: EdgeInsets.only(top: 12), child: Row( children: [ GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { showPopupDeletePhoto(index, boxImageSize); }, child: Container( padding: EdgeInsets.fromLTRB(5, 0, 5, 0), height: 30, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( width: 1, color: Colors.grey[300]!)), child: Icon(Icons.delete, color: _color1, size: 20), ), ), SizedBox( width: 8, ), Expanded( child: (1 == 0) // (productData.stock == 0) ? TextButton( style: ButtonStyle( minimumSize: MaterialStateProperty.all(Size(0, 30)), backgroundColor: MaterialStateProperty.resolveWith( (Set states) => Colors.grey[300]!, ), overlayColor: MaterialStateProperty.all( Colors.transparent), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), )), ), onPressed: null, child: Text( 'Out of Stock', style: TextStyle( color: Colors.grey[600], fontWeight: FontWeight.bold, fontSize: 13), textAlign: TextAlign.center, )) : OutlinedButton( onPressed: () { Fluttertoast.showToast( msg: 'Item has been added to Shopping Cart'); }, style: ButtonStyle( minimumSize: MaterialStateProperty.all(Size(0, 30)), overlayColor: MaterialStateProperty.all( Colors.transparent), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.0), )), side: MaterialStateProperty.all( BorderSide(color: SOFT_BLUE, width: 1.0), )), child: Text( 'Copier dans galerie', style: TextStyle( color: SOFT_BLUE, fontWeight: FontWeight.bold, fontSize: 13), textAlign: TextAlign.center, )), ), SizedBox( width: 8, ), GestureDetector( behavior: HitTestBehavior.translucent, 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), height: 30, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( width: 1, color: Colors.grey[300]!)), child: Icon(Icons.rotate_right_rounded, color: _color1, size: 20), ), ), SizedBox( width: 8, ), GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { showPopupDeletePhoto(index, boxImageSize); }, child: Container( padding: EdgeInsets.fromLTRB(5, 0, 5, 0), height: 30, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( width: 1, color: Colors.grey[300]!)), child: Icon(Icons.rotate_left_rounded, color: _color1, size: 20), ), ) ], ), ) ], ), ), ), ), ); } Widget fabCart(context) { return FloatingActionButton( onPressed: () { photoFiles.clear(); Route route = MaterialPageRoute( builder: (context) => CameraPage(photoFiles: photoFiles)); Navigator.push(context, route).then((val) { /// if the user has validated photos if (val == true) { savePhotos(); } else { resetPhotos(); } }); }, child: Stack(children: [ Icon(Icons.add_a_photo, color: BLACK21, size: 42), Positioned( right: 0, bottom: 0, child: Container( padding: EdgeInsets.all(1), decoration: BoxDecoration( color: PRIMARY_COLOR, borderRadius: BorderRadius.circular(14), ), constraints: BoxConstraints( minWidth: 16, minHeight: 16, ), child: Center( child: Text( _photoData.length.toString(), style: TextStyle( color: Colors.white, fontSize: 8, ), textAlign: TextAlign.center, ), ), ), ) ]), backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(4.0))), ); } void savePhotos() { if (photoFiles.length > 0) { final List _listPhotos = []; final List _listPhotosModel = []; 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); /// insert photo(s) in widget _photoData.insertAll(0, _listPhotosModel); /// refresh widget setState(() => _listKey = GlobalKey()); } } void resetPhotos() { /// for all photos taken for (var photo in photoFiles) { /// delete image on local storage deleteFile(photo); } } 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: () { Navigator.pop(context); }, child: Text('No', style: TextStyle(color: SOFT_BLUE))); Widget continueButton = TextButton( 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. AnimatedRemovedItemBuilder builder = (context, animation) { // A method to build the Card widget. return _buildPhotolistCard( removedItem, boxImageSize, animation, removeIndex); }; _listKey.currentState!.removeItem(removeIndex, builder); Navigator.pop(context); Fluttertoast.showToast( msg: 'Photo has been deleted from your visit', toastLength: Toast.LENGTH_SHORT); }, child: Text('Yes', style: TextStyle(color: SOFT_BLUE))); // set up the AlertDialog AlertDialog alert = AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), title: Text( 'Delete Photo', style: TextStyle(fontSize: 18), ), content: Text('Are you sure to delete this photo from your visit ?', style: TextStyle(fontSize: 13, color: _color1)), actions: [ cancelButton, continueButton, ], ); // show the dialog showDialog( context: context, builder: (BuildContext context) { return alert; }, ); } Future rotateAndReplaceImage(File imageFile) async { // Read the image file into a Uint8List Uint8List bytes = await imageFile.readAsBytes(); // Decode the bytes into an Image object using the image package img.Image? image = img.decodeImage(bytes); // Rotate the image clockwise by 90 degrees img.Image? rotatedImage = img.copyRotate(image!, angle: 10); // 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); } }