diff --git a/lib/config/constant.dart b/lib/config/constant.dart index ad2abba..6d3cbe9 100644 --- a/lib/config/constant.dart +++ b/lib/config/constant.dart @@ -46,3 +46,5 @@ class ApiConstants { const String LOGIN_API = 'https://mp4.ikksgroup.com' + "/authentication/login"; const String PRODUCT_API = 'https://mp4.ikksgroup.com' + "/example/getProduct"; +const String SERVLET_API = + 'https://mp4.ikksgroup.com' + "/MobilePortal4_external/UploadPhotoServlet"; diff --git a/lib/model/visite_model.dart b/lib/model/visite_model.dart index f15a56c..dec3922 100644 --- a/lib/model/visite_model.dart +++ b/lib/model/visite_model.dart @@ -1,3 +1,4 @@ +import 'package:intl/intl.dart'; import 'package:mobdr/main.dart'; class VisiteModel { @@ -34,7 +35,8 @@ class VisiteModel { id_visite: visite.id_visite, name: visite.id_etab.toString() + ' - ' + visite.title, 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, type_visite: visite.type_visite, langage: visite.langage)) @@ -56,7 +58,8 @@ class VisiteModel { id_visite: visite.id_visite, name: visite.id_etab.toString() + ' - ' + visite.title, 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, type_visite: visite.type_visite, langage: visite.langage)) @@ -78,7 +81,8 @@ class VisiteModel { id_visite: visite.id_visite, name: visite.id_etab.toString() + ' - ' + visite.title, 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, type_visite: visite.type_visite, langage: visite.langage)) diff --git a/lib/objectbox.dart b/lib/objectbox.dart index 54035a7..37e9c14 100644 --- a/lib/objectbox.dart +++ b/lib/objectbox.dart @@ -525,7 +525,7 @@ class ObjectBox { final results = query.find(); if (results.isNotEmpty) { final photoToDelete = results.first; - visitPhotoBox.removeAsync(photoToDelete.id); + visitPhotoBox.remove(photoToDelete.id); } } diff --git a/lib/ui/home/tab_home.dart b/lib/ui/home/tab_home.dart index 8238769..ab49690 100644 --- a/lib/ui/home/tab_home.dart +++ b/lib/ui/home/tab_home.dart @@ -256,7 +256,6 @@ class _TabHomePageState extends State pp_langage: data.langage, pp_id_visite: data.id_visite, pp_name: data.name, - onRefreshVisit: (int photoCount) {}, ), ); Navigator.push(context, route); diff --git a/lib/ui/sync/upload_photos.dart b/lib/ui/sync/upload_photos.dart new file mode 100644 index 0000000..3983ea8 --- /dev/null +++ b/lib/ui/sync/upload_photos.dart @@ -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 photoPaths; + + UploadPhotosPage({required this.id_visite, required this.photoPaths}); + + @override + _UploadPhotosPageState createState() => _UploadPhotosPageState(); +} + +class _UploadPhotosPageState extends State { + bool _isUploading = false; + bool _isFinished = false; + int _totalUploaded = 0; + int _totalPhotos = 0; + + @override + void initState() { + super.initState(); + + _totalPhotos = widget.photoPaths.length; + } + + Future _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 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( + 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, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/ui/visit/tab_visit.dart b/lib/ui/visit/tab_visit.dart index 7aeb7a4..4e40d54 100644 --- a/lib/ui/visit/tab_visit.dart +++ b/lib/ui/visit/tab_visit.dart @@ -8,6 +8,7 @@ import 'package:mobdr/events.dart'; import 'package:mobdr/model/visite_model.dart'; import 'package:mobdr/ui/visit/visit_photo_typology.dart'; import 'package:mobdr/ui/reusable/cache_image_network.dart'; +import 'package:mobdr/ui/sync/upload_photos.dart'; class TabVisitListPage extends StatefulWidget { @override @@ -68,7 +69,7 @@ class _TabVisitListPageState extends State return Center(child: CircularProgressIndicator()); } else if (modelData.isEmpty) { return Center( - child: Text('Aucune visite trouvée.'), + child: Text('No visits to synchronise.'), ); } return Scaffold( @@ -164,12 +165,10 @@ class _TabVisitListPageState extends State onTap: () { Route route = MaterialPageRoute( builder: (context) => VisitPhotoTypologyPage( - pp_id_distrib: visiteData.id_distrib, - pp_langage: visiteData.langage, - pp_id_visite: visiteData.id_visite, - pp_name: visiteData.name, - onRefreshVisit: (int photoCount) {}, - )); + pp_id_distrib: visiteData.id_distrib, + pp_langage: visiteData.langage, + pp_id_visite: visiteData.id_visite, + pp_name: visiteData.name)); Navigator.push(context, route); }, child: Container( @@ -208,8 +207,7 @@ class _TabVisitListPageState extends State child: Row( children: [ Text(visiteData.name, - style: TextStyle( - fontSize: 11, color: SOFT_GREY)) + style: GlobalStyle.productPrice) ], ), ), @@ -273,7 +271,7 @@ class _TabVisitListPageState extends State borderRadius: BorderRadius.circular(5.0), )), ), - onPressed: null, + onPressed: () {}, child: Text( 'Synchronize', style: TextStyle( @@ -283,7 +281,17 @@ class _TabVisitListPageState extends State textAlign: TextAlign.center, )) : OutlinedButton( - onPressed: () {}, + onPressed: () { + Route route = MaterialPageRoute( + builder: (context) => UploadPhotosPage( + id_visite: visiteData.id_visite, + photoPaths: [ + 'sim_1682957196322406.jpeg', + ], + ), + ); + Navigator.push(context, route); + }, style: ButtonStyle( minimumSize: MaterialStateProperty.all( Size(0, 30)), diff --git a/lib/ui/visit/visit_photo_typology.dart b/lib/ui/visit/visit_photo_typology.dart index d8291b1..b63aee2 100644 --- a/lib/ui/visit/visit_photo_typology.dart +++ b/lib/ui/visit/visit_photo_typology.dart @@ -13,16 +13,14 @@ class VisitPhotoTypologyPage extends StatefulWidget { final String pp_langage; final int pp_id_visite; final String pp_name; - final Function(int) onRefreshVisit; - VisitPhotoTypologyPage( - {Key? key, - required this.pp_id_distrib, - required this.pp_langage, - required this.pp_id_visite, - required this.pp_name, - required this.onRefreshVisit}) - : super(key: key); + VisitPhotoTypologyPage({ + Key? key, + required this.pp_id_distrib, + required this.pp_langage, + required this.pp_id_visite, + required this.pp_name, + }) : super(key: key); @override _VisitPhotoTypologyPageState createState() => _VisitPhotoTypologyPageState(); @@ -127,42 +125,37 @@ class _VisitPhotoTypologyPageState extends State { setState(() {}); } - Future 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 Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - return onBackPressed(); - }, - child: Scaffold( - backgroundColor: Colors.white, - appBar: AppBar( - iconTheme: IconThemeData( - color: GlobalStyle.appBarIconThemeColor, - ), - elevation: GlobalStyle.appBarElevation, - title: Text( - widget.pp_name, - style: GlobalStyle.appBarTitle, - ), - backgroundColor: GlobalStyle.appBarBackgroundColor, - systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle), - body: Column(children: [ - Expanded( - child: StreamBuilder>( - stream: objectbox.getPhotoTypologies(), - builder: (context, snapshot) => ListView.builder( - shrinkWrap: true, - //padding: const EdgeInsets.symmetric(horizontal: 20.0), - itemCount: - snapshot.hasData ? snapshot.data!.length : 0, - itemBuilder: _itemBuilder(snapshot.data ?? [])))) - ]))); + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + iconTheme: IconThemeData( + color: GlobalStyle.appBarIconThemeColor, + ), + elevation: GlobalStyle.appBarElevation, + title: Text( + widget.pp_name, + style: GlobalStyle.appBarTitle, + ), + backgroundColor: GlobalStyle.appBarBackgroundColor, + systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle, + ), + body: Column( + children: [ + Expanded( + child: StreamBuilder>( + stream: objectbox.getPhotoTypologies(), + builder: (context, snapshot) => ListView.builder( + shrinkWrap: true, + //padding: const EdgeInsets.symmetric(horizontal: 20.0), + itemCount: snapshot.hasData ? snapshot.data!.length : 0, + itemBuilder: _itemBuilder(snapshot.data ?? []), + ), + ), + ), + ], + ), + ); } }