mobdr/lib/ui/home/photo_list.dart

550 lines
21 KiB
Dart

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:path_provider/path_provider.dart';
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/db/box_photo.dart';
import 'package:mobdr/ui/home/photo_detail.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 {
@override
_PhotoListPageState createState() => _PhotoListPageState();
}
class _PhotoListPageState extends State<PhotoListPage> {
final _globalWidget = GlobalWidget();
// initialize photos files list
final List<File> photoFiles = [];
List<Photo> _photoData = [];
Color _color1 = Color(0xff777777);
Color _color2 = Color(0xFF515151);
// _listKey is used for AnimatedList
var _listKey = GlobalKey<AnimatedListState>();
@override
void initState() {
super.initState();
_loadData();
}
@override
void dispose() {
super.dispose();
}
void _loadData() {
_photoData = objectbox.getAllPhotos();
}
@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(Photo 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: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoDetailPage(
id: _photoData[index].id,
name: 'visiteData.name',
image: _photoData[index].image,
price: 0,
photo: 1,
rating: 0,
review: 0,
sale: 0,
date: '')));
// todo if qq chose à changé
// ne raffraichir que le widget en cours ==
setState(() {
_listKey = GlobalKey();
});
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
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<Color>(
(Set<MaterialState> 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), 90);
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: () async {
await rotateAndReplaceImage(
File(_photoData[index].image), -90);
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_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) {
// Restore the constraint before navigating away
// DO await here to avoid any other screen in
// landscape mode
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
/// if the user has validated photos
if (val == true) {
savePhotos();
}
});
},
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))),
);
}
Future<bool> evictImage(String imageURL) async {
final NetworkImage provider = NetworkImage(imageURL);
return await provider.evict();
}
void savePhotos() async {
if (photoFiles.length > 0) {
final List<Photo> _listPhotos = [];
for (var myTmpPhoto in photoFiles) {
/// move jpg file to photo directory
final myPhoto = await moveFileFromTempToPhotosDir(myTmpPhoto);
/// to insert into database
_listPhotos.add(Photo(
id_visite: 0,
id_photo_typologie: 0,
image: myPhoto.path,
image_name: myPhoto.path.split('/').last));
}
/// insert photo(s) in database (async)
final addedPhotos = await objectbox.addPhotos(_listPhotos);
/// insert photo(s) in widget at the beginning (0)
_photoData.insertAll(0, addedPhotos);
/// refresh widget
setState(() {
_listKey = GlobalKey();
});
}
}
Future<void> 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 local storage
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<File> moveFileFromTempToPhotosDir(File tempFile) async {
// Get the application's document directory
final Directory documentsDir = await getApplicationDocumentsDirectory();
// Set the new file path with the original file name
final String newPath =
'${documentsDir.path}/photos/${tempFile.path.split('/').last}';
// 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<void> rotateAndReplaceImage(File imageFile, int angle) 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: 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();
}
}