515 lines
19 KiB
Dart
515 lines
19 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: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<PhotoListPage> {
|
|
final _globalWidget = GlobalWidget();
|
|
|
|
// initialize photos files list
|
|
final List<File> photoFiles = [];
|
|
List<PhotoModel> _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() {
|
|
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: <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));
|
|
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<Photo> _listPhotos = [];
|
|
final List<PhotoModel> _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<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 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<void> 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);
|
|
}
|
|
}
|