feat: upload photo

release/mobdr-v0.0.1
Frédérik Benoist 2023-05-01 23:57:35 +02:00
parent abb13b588b
commit 492851ffc9
7 changed files with 253 additions and 60 deletions

View File

@ -46,3 +46,5 @@ class ApiConstants {
const String LOGIN_API = 'https://mp4.ikksgroup.com' + "/authentication/login"; const String LOGIN_API = 'https://mp4.ikksgroup.com' + "/authentication/login";
const String PRODUCT_API = 'https://mp4.ikksgroup.com' + "/example/getProduct"; const String PRODUCT_API = 'https://mp4.ikksgroup.com' + "/example/getProduct";
const String SERVLET_API =
'https://mp4.ikksgroup.com' + "/MobilePortal4_external/UploadPhotoServlet";

View File

@ -1,3 +1,4 @@
import 'package:intl/intl.dart';
import 'package:mobdr/main.dart'; import 'package:mobdr/main.dart';
class VisiteModel { class VisiteModel {
@ -34,7 +35,8 @@ class VisiteModel {
id_visite: visite.id_visite, id_visite: visite.id_visite,
name: visite.id_etab.toString() + ' - ' + visite.title, name: visite.id_etab.toString() + ' - ' + visite.title,
photoCount: objectbox.getVisitPhotoCount(visite.id_visite), photoCount: objectbox.getVisitPhotoCount(visite.id_visite),
date: visite.date_visite.toString(), date: DateFormat('EEEE d MMMM HH:mm', 'fr_FR')
.format(visite.date_visite),
image: visite.url_photo_principale, image: visite.url_photo_principale,
type_visite: visite.type_visite, type_visite: visite.type_visite,
langage: visite.langage)) langage: visite.langage))
@ -56,7 +58,8 @@ class VisiteModel {
id_visite: visite.id_visite, id_visite: visite.id_visite,
name: visite.id_etab.toString() + ' - ' + visite.title, name: visite.id_etab.toString() + ' - ' + visite.title,
photoCount: objectbox.getVisitPhotoCount(visite.id_visite), photoCount: objectbox.getVisitPhotoCount(visite.id_visite),
date: visite.date_visite.toString(), date: DateFormat('EEEE d MMMM HH:mm', 'fr_FR')
.format(visite.date_visite),
image: visite.url_photo_principale, image: visite.url_photo_principale,
type_visite: visite.type_visite, type_visite: visite.type_visite,
langage: visite.langage)) langage: visite.langage))
@ -78,7 +81,8 @@ class VisiteModel {
id_visite: visite.id_visite, id_visite: visite.id_visite,
name: visite.id_etab.toString() + ' - ' + visite.title, name: visite.id_etab.toString() + ' - ' + visite.title,
photoCount: objectbox.getVisitPhotoCount(visite.id_visite), photoCount: objectbox.getVisitPhotoCount(visite.id_visite),
date: visite.date_visite.toString(), date: DateFormat('EEEE d MMMM HH:mm', 'fr_FR')
.format(visite.date_visite),
image: visite.url_photo_principale, image: visite.url_photo_principale,
type_visite: visite.type_visite, type_visite: visite.type_visite,
langage: visite.langage)) langage: visite.langage))

View File

@ -525,7 +525,7 @@ class ObjectBox {
final results = query.find(); final results = query.find();
if (results.isNotEmpty) { if (results.isNotEmpty) {
final photoToDelete = results.first; final photoToDelete = results.first;
visitPhotoBox.removeAsync(photoToDelete.id); visitPhotoBox.remove(photoToDelete.id);
} }
} }

View File

@ -256,7 +256,6 @@ class _TabHomePageState extends State<TabHomePage>
pp_langage: data.langage, pp_langage: data.langage,
pp_id_visite: data.id_visite, pp_id_visite: data.id_visite,
pp_name: data.name, pp_name: data.name,
onRefreshVisit: (int photoCount) {},
), ),
); );
Navigator.push(context, route); Navigator.push(context, route);

View File

@ -0,0 +1,187 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as path;
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mobdr/service/shared_prefs.dart';
import 'package:mobdr/config/constant.dart';
class UploadPhotosPage extends StatefulWidget {
final int id_visite;
final List<String> photoPaths;
UploadPhotosPage({required this.id_visite, required this.photoPaths});
@override
_UploadPhotosPageState createState() => _UploadPhotosPageState();
}
class _UploadPhotosPageState extends State<UploadPhotosPage> {
bool _isUploading = false;
bool _isFinished = false;
int _totalUploaded = 0;
int _totalPhotos = 0;
@override
void initState() {
super.initState();
_totalPhotos = widget.photoPaths.length;
}
Future<void> _uploadPhotos() async {
setState(() {
_isUploading = true;
_isFinished = false;
});
for (int i = 0; i < widget.photoPaths.length; i++) {
String photoPath = SharedPrefs().photosDir + "/" + widget.photoPaths[i];
//String filename = path.basename(photoPath);
// Upload the photo
int photoId = await uploadPhoto(photoPath);
if (photoId != -1) {
_totalUploaded++;
}
}
setState(() {
_isUploading = false;
_isFinished = true;
});
}
Future<int> uploadPhoto(String photoPath) async {
try {
final url = Uri.parse(SERVLET_API);
final file = File(photoPath);
final bytes = await file.readAsBytes();
final multipartRequest = http.MultipartRequest('POST', url)
..fields['id_visite'] = widget.id_visite.toString()
..files.add(http.MultipartFile.fromBytes(
'photo',
bytes,
filename: path.basename(photoPath),
contentType: MediaType('image', 'jpeg'),
));
final headers = {'Cookie': "pguid=${SharedPrefs().guid};"};
multipartRequest.headers.addAll(headers);
final response = await multipartRequest.send();
final responseString = await response.stream.bytesToString();
final jsonResponse = jsonDecode(responseString);
final idPhotoString = jsonResponse[0]['id_photo'];
final idPhoto = int.parse(idPhotoString);
return idPhoto;
} catch (e) {
print('Error uploading photo: $e');
return -1;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Upload Photos'),
centerTitle: true,
),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Upload Photos',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
Expanded(
child: Stack(
children: [
Center(
child: Visibility(
visible: _isUploading,
child: Container(
height: 150,
width: 150,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor,
),
strokeWidth: 10,
),
),
),
),
Center(
child: Visibility(
visible: !_isUploading && _totalUploaded == _totalPhotos,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
size: 48,
color: Theme.of(context).primaryColor,
),
SizedBox(height: 16),
Text(
'Upload complete',
textAlign: TextAlign.center,
),
],
),
),
),
Center(
child: Visibility(
visible: !_isUploading && _totalUploaded != _totalPhotos,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.cloud_upload,
size: 48,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'Tap the button below to upload your photos',
textAlign: TextAlign.center,
),
],
),
),
),
],
),
),
SizedBox(height: 16),
Visibility(
visible: !_isUploading,
child: ElevatedButton(
child: Text('Upload Photos ($_totalUploaded / $_totalPhotos)'),
onPressed: _isFinished ? null : _uploadPhotos,
),
),
],
),
),
);
}
}

View File

@ -8,6 +8,7 @@ import 'package:mobdr/events.dart';
import 'package:mobdr/model/visite_model.dart'; import 'package:mobdr/model/visite_model.dart';
import 'package:mobdr/ui/visit/visit_photo_typology.dart'; import 'package:mobdr/ui/visit/visit_photo_typology.dart';
import 'package:mobdr/ui/reusable/cache_image_network.dart'; import 'package:mobdr/ui/reusable/cache_image_network.dart';
import 'package:mobdr/ui/sync/upload_photos.dart';
class TabVisitListPage extends StatefulWidget { class TabVisitListPage extends StatefulWidget {
@override @override
@ -68,7 +69,7 @@ class _TabVisitListPageState extends State<TabVisitListPage>
return Center(child: CircularProgressIndicator()); return Center(child: CircularProgressIndicator());
} else if (modelData.isEmpty) { } else if (modelData.isEmpty) {
return Center( return Center(
child: Text('Aucune visite trouvée.'), child: Text('No visits to synchronise.'),
); );
} }
return Scaffold( return Scaffold(
@ -167,9 +168,7 @@ class _TabVisitListPageState extends State<TabVisitListPage>
pp_id_distrib: visiteData.id_distrib, pp_id_distrib: visiteData.id_distrib,
pp_langage: visiteData.langage, pp_langage: visiteData.langage,
pp_id_visite: visiteData.id_visite, pp_id_visite: visiteData.id_visite,
pp_name: visiteData.name, pp_name: visiteData.name));
onRefreshVisit: (int photoCount) {},
));
Navigator.push(context, route); Navigator.push(context, route);
}, },
child: Container( child: Container(
@ -208,8 +207,7 @@ class _TabVisitListPageState extends State<TabVisitListPage>
child: Row( child: Row(
children: [ children: [
Text(visiteData.name, Text(visiteData.name,
style: TextStyle( style: GlobalStyle.productPrice)
fontSize: 11, color: SOFT_GREY))
], ],
), ),
), ),
@ -273,7 +271,7 @@ class _TabVisitListPageState extends State<TabVisitListPage>
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(5.0),
)), )),
), ),
onPressed: null, onPressed: () {},
child: Text( child: Text(
'Synchronize', 'Synchronize',
style: TextStyle( style: TextStyle(
@ -283,7 +281,17 @@ class _TabVisitListPageState extends State<TabVisitListPage>
textAlign: TextAlign.center, textAlign: TextAlign.center,
)) ))
: OutlinedButton( : OutlinedButton(
onPressed: () {}, onPressed: () {
Route route = MaterialPageRoute(
builder: (context) => UploadPhotosPage(
id_visite: visiteData.id_visite,
photoPaths: [
'sim_1682957196322406.jpeg',
],
),
);
Navigator.push(context, route);
},
style: ButtonStyle( style: ButtonStyle(
minimumSize: MaterialStateProperty.all( minimumSize: MaterialStateProperty.all(
Size(0, 30)), Size(0, 30)),

View File

@ -13,16 +13,14 @@ class VisitPhotoTypologyPage extends StatefulWidget {
final String pp_langage; final String pp_langage;
final int pp_id_visite; final int pp_id_visite;
final String pp_name; final String pp_name;
final Function(int) onRefreshVisit;
VisitPhotoTypologyPage( VisitPhotoTypologyPage({
{Key? key, Key? key,
required this.pp_id_distrib, required this.pp_id_distrib,
required this.pp_langage, required this.pp_langage,
required this.pp_id_visite, required this.pp_id_visite,
required this.pp_name, required this.pp_name,
required this.onRefreshVisit}) }) : super(key: key);
: super(key: key);
@override @override
_VisitPhotoTypologyPageState createState() => _VisitPhotoTypologyPageState(); _VisitPhotoTypologyPageState createState() => _VisitPhotoTypologyPageState();
@ -127,20 +125,9 @@ class _VisitPhotoTypologyPageState extends State<VisitPhotoTypologyPage> {
setState(() {}); setState(() {});
} }
Future<bool> onBackPressed() async {
// Navigate back to the visits page and refresh the data
int newPhotoCount = objectbox.getVisitPhotoCount(widget.pp_id_visite);
widget.onRefreshVisit(newPhotoCount);
return true;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WillPopScope( return Scaffold(
onWillPop: () async {
return onBackPressed();
},
child: Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: AppBar( appBar: AppBar(
iconTheme: IconThemeData( iconTheme: IconThemeData(
@ -152,17 +139,23 @@ class _VisitPhotoTypologyPageState extends State<VisitPhotoTypologyPage> {
style: GlobalStyle.appBarTitle, style: GlobalStyle.appBarTitle,
), ),
backgroundColor: GlobalStyle.appBarBackgroundColor, backgroundColor: GlobalStyle.appBarBackgroundColor,
systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle), systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle,
body: Column(children: <Widget>[ ),
body: Column(
children: <Widget>[
Expanded( Expanded(
child: StreamBuilder<List<PhotoTypology>>( child: StreamBuilder<List<PhotoTypology>>(
stream: objectbox.getPhotoTypologies(), stream: objectbox.getPhotoTypologies(),
builder: (context, snapshot) => ListView.builder( builder: (context, snapshot) => ListView.builder(
shrinkWrap: true, shrinkWrap: true,
//padding: const EdgeInsets.symmetric(horizontal: 20.0), //padding: const EdgeInsets.symmetric(horizontal: 20.0),
itemCount: itemCount: snapshot.hasData ? snapshot.data!.length : 0,
snapshot.hasData ? snapshot.data!.length : 0, itemBuilder: _itemBuilder(snapshot.data ?? []),
itemBuilder: _itemBuilder(snapshot.data ?? [])))) ),
]))); ),
),
],
),
);
} }
} }