From 06b33e9ec68b920a9deb8f461d7bc6696c8b5e7e Mon Sep 17 00:00:00 2001 From: Frederik Benoist Date: Mon, 22 May 2023 21:20:10 +0200 Subject: [PATCH] refactor:synchronization (step 1) --- assets/images/synchro.png | Bin 0 -> 37154 bytes lib/config/global_style.dart | 2 +- lib/model/visit_model.dart | 38 +- lib/objectbox.dart | 3 - lib/old/tab_sync.dart | 1 - lib/ui/home.dart | 50 +- lib/ui/home/tab_home.dart | 141 +++- ..._calendar.dart => sync_calendar copy.dart} | 6 +- lib/ui/sync/synchronization copy.dart | 451 +++++++++++++ lib/ui/sync/synchronization.dart | 611 ++++++------------ lib/ui/sync/testsync copy.dart | 228 +++++++ lib/ui/sync/upload_photos.dart | 113 +++- lib/ui/visit/visit_photo_typology_list.dart | 5 - pubspec.yaml | 1 + 14 files changed, 1159 insertions(+), 491 deletions(-) create mode 100644 assets/images/synchro.png rename lib/ui/sync/{sync_calendar.dart => sync_calendar copy.dart} (94%) create mode 100644 lib/ui/sync/synchronization copy.dart create mode 100644 lib/ui/sync/testsync copy.dart diff --git a/assets/images/synchro.png b/assets/images/synchro.png new file mode 100644 index 0000000000000000000000000000000000000000..4e6c5f97a9e5d265d695531a8acd47f1eba67a05 GIT binary patch literal 37154 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

H0Pary>82&p9Fo6k?%RL;3-epFHyid071hB9NP5 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 D9Lhr2 literal 0 HcmV?d00001 diff --git a/lib/config/global_style.dart b/lib/config/global_style.dart index 8ec2e87..d8c2502 100644 --- a/lib/config/global_style.dart +++ b/lib/config/global_style.dart @@ -178,7 +178,7 @@ class GlobalStyle { TextStyle(fontSize: 18, fontWeight: FontWeight.bold); static const TextStyle cardTitle = - TextStyle(fontSize: 14, color: Colors.black, fontWeight: FontWeight.bold); + TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold); static const TextStyle textPromo = TextStyle( fontSize: 12, diff --git a/lib/model/visit_model.dart b/lib/model/visit_model.dart index d51e88d..cc955c4 100644 --- a/lib/model/visit_model.dart +++ b/lib/model/visit_model.dart @@ -12,18 +12,21 @@ class VisitModel { late String image; late String type_visite; late String langage; + late DateTime? date_validation; - VisitModel( - {required this.id, - required this.id_distrib, - required this.id_etab, - required this.id_visite, - required this.name, - required this.photoCount, - required this.date, - required this.image, - required this.type_visite, - required this.langage}); + VisitModel({ + required this.id, + required this.id_distrib, + required this.id_etab, + required this.id_visite, + required this.name, + required this.photoCount, + required this.date, + required this.image, + required this.type_visite, + required this.langage, + required this.date_validation, + }); static Future> getTodayVisits() async { // Retrieve all today visits from the database @@ -42,7 +45,8 @@ class VisitModel { .format(visite.date_visite), image: visite.url_photo_principale, type_visite: visite.type_visite, - langage: visite.langage)) + langage: visite.langage, + date_validation: visite.date_validation)) .toList(); // Return the list of VisiteModel @@ -66,16 +70,17 @@ class VisitModel { .format(visite.date_visite), image: visite.url_photo_principale, type_visite: visite.type_visite, - langage: visite.langage)) + langage: visite.langage, + date_validation: visite.date_validation)) .toList(); // Return the list of VisiteModel return visitModelList; } - static List getToSyncVisits() { + static Future> getToSyncVisits() async { // Retrieve all visits from the database - final visits = objectbox.getAllVisit(); + final visits = await objectbox.getAllVisit(); // Map each retrieved visit to VisiteModel final visitModelList = visits @@ -90,7 +95,8 @@ class VisitModel { .format(visite.date_visite), image: visite.url_photo_principale, type_visite: visite.type_visite, - langage: visite.langage)) + langage: visite.langage, + date_validation: visite.date_validation)) .where((visit) => visit.photoCount > 0) // Filter out visits with photoCount equal to 0 diff --git a/lib/objectbox.dart b/lib/objectbox.dart index 34aef1b..038e302 100644 --- a/lib/objectbox.dart +++ b/lib/objectbox.dart @@ -78,9 +78,6 @@ class ObjectBox { .map((query) => query.find()); } - static void _putNotesInTx(Store store, List notes) => - store.box().putMany(notes); - /// Add a note within a transaction. /// /// To avoid frame drops, run ObjectBox operations that take longer than a diff --git a/lib/old/tab_sync.dart b/lib/old/tab_sync.dart index 43a5307..0b881c7 100644 --- a/lib/old/tab_sync.dart +++ b/lib/old/tab_sync.dart @@ -6,7 +6,6 @@ import 'package:mobdr/config/global_style.dart'; import 'package:mobdr/ui/reusable/reusable_widget.dart'; import 'package:mobdr/network/api_provider.dart'; import 'package:mobdr/main.dart'; -import 'package:mobdr/ui/home/tab_home.dart'; const completeColor = Color(0xff5e6172); const inProgressColor = Color(0xff5ec792); diff --git a/lib/ui/home.dart b/lib/ui/home.dart index f7c0f19..210418d 100644 --- a/lib/ui/home.dart +++ b/lib/ui/home.dart @@ -77,38 +77,44 @@ class _HomePageState extends State type: BottomNavigationBarType.fixed, currentIndex: _currentIndex, onTap: (value) { - _currentIndex = value; - _pageController.jumpToPage(value); - // this unfocus is to prevent show keyboard in the wishlist page when focus on search text field - FocusScope.of(context).unfocus(); + setState(() { + _currentIndex = value; + _pageController.jumpToPage(value); + FocusScope.of(context).unfocus(); + }); }, selectedFontSize: 8, unselectedFontSize: 8, iconSize: 28, + selectedItemColor: + Colors.blue, // Couleur de l'icône de l'onglet sélectionné + unselectedItemColor: + CHARCOAL, // Couleur de l'icône des onglets non sélectionnés selectedLabelStyle: TextStyle( - color: _currentIndex == 1 ? ASSENT_COLOR : PRIMARY_COLOR, - fontWeight: FontWeight.bold), - unselectedLabelStyle: - TextStyle(color: CHARCOAL, fontWeight: FontWeight.bold), - selectedItemColor: _currentIndex == 1 ? ASSENT_COLOR : PRIMARY_COLOR, - unselectedItemColor: CHARCOAL, + color: Colors.blue, // Couleur du texte de l'onglet sélectionné + fontWeight: FontWeight.bold, + ), + unselectedLabelStyle: TextStyle( + color: CHARCOAL, // Couleur du texte des onglets non sélectionnés + fontWeight: FontWeight.bold, + ), items: [ BottomNavigationBarItem( - label: 'Home', - icon: Icon(Icons.home, - color: _currentIndex == 0 ? PRIMARY_COLOR : CHARCOAL)), + label: 'Home', + icon: Icon(Icons.home), + ), BottomNavigationBarItem( - label: 'Synchro', - icon: Icon(Icons.sync, - color: _currentIndex == 1 ? ASSENT_COLOR : CHARCOAL)), + label: 'Synchro', + icon: Icon(Icons.sync), + ), BottomNavigationBarItem( - label: 'MP4', - icon: Icon(Icons.web, - color: _currentIndex == 2 ? PRIMARY_COLOR : CHARCOAL)), + label: 'MP4', + icon: Icon(Icons.web), + ), BottomNavigationBarItem( - label: 'Account', - icon: Icon(Icons.person_outline, - color: _currentIndex == 3 ? PRIMARY_COLOR : CHARCOAL)), + label: 'Account', + icon: Icon(Icons.person_outline), + ), ], ), ); diff --git a/lib/ui/home/tab_home.dart b/lib/ui/home/tab_home.dart index 47b775b..c422cb4 100644 --- a/lib/ui/home/tab_home.dart +++ b/lib/ui/home/tab_home.dart @@ -18,7 +18,7 @@ import 'package:mobdr/cubit/language/app_localizations.dart'; import 'package:mobdr/service/shared_prefs.dart'; import 'package:mobdr/config/global_style.dart'; import 'package:mobdr/events.dart'; -import 'package:mobdr/ui/sync/sync_calendar.dart'; +import 'package:mobdr/ui/sync/synchronization.dart'; import 'package:mobdr/model/visit_model.dart'; import 'package:mobdr/ui/visit/visit_photo_typology.dart'; import 'package:mobdr/ui/general/chat_us.dart'; @@ -188,7 +188,7 @@ class _TabHomePageState extends State return Column( children: [ Container( - padding: EdgeInsets.fromLTRB(16, 16, 16, 0), + padding: EdgeInsets.fromLTRB(16, 8, 0, 0), child: Row( children: [ Text('Last visit access', style: GlobalStyle.horizontalTitle), @@ -197,10 +197,9 @@ class _TabHomePageState extends State ), Container( margin: EdgeInsets.only(top: 8), - height: boxImageSize * GlobalStyle.cardHeightMultiplication, alignment: Alignment.centerLeft, padding: EdgeInsets.symmetric(horizontal: 12), - child: buildHorizontalVisitListCard(context, lastVisitData)), + child: buildVisitListCard(context, lastVisitData!)), ], ); } @@ -238,13 +237,12 @@ class _TabHomePageState extends State Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox(height: 16), // Ajout de l'espace ici - Text('Aucune visite ce jour'), - ElevatedButton( + TextButton( onPressed: () async { final result = await Navigator.push( context, - MaterialPageRoute(builder: (context) => SyncCalendarPage()), + MaterialPageRoute( + builder: (context) => SynchronizationPage()), ); // Refresh the widget if the synchronization was successful. @@ -254,7 +252,20 @@ class _TabHomePageState extends State }); } }, - child: Text('Synchroniser'), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Synchronize', + style: TextStyle( + color: Colors.blue, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Icon(Icons.chevron_right, color: Colors.blue), + ], + ), ), ], ) @@ -308,7 +319,100 @@ class _TabHomePageState extends State ); } - Widget buildHorizontalVisitListCard(context, data) { + Widget buildVisitListCard(BuildContext context, VisitModel data) { + final double boxImageSize = (MediaQuery.of(context).size.width / 4); + return GestureDetector( + onTap: () { + Route route = MaterialPageRoute( + builder: (context) => VisitPhotoTypologyPage(pp_visitModel: data), + ); + Navigator.push(context, route); + }, + child: Container( + margin: EdgeInsets.fromLTRB(0, 0, 0, 0), + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 2, + color: Colors.white, + child: Container( + margin: EdgeInsets.all(8), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(10)), + child: buildCacheNetworkImage( + width: boxImageSize, + height: boxImageSize, + url: data.image, + ), + ), + SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 5), + child: Row( + children: [ + Text(data.name, + style: GlobalStyle.productPrice), + ], + ), + ), + Container(height: 8), + Text( + data.date, + style: GlobalStyle.productSale, + ), + Container( + margin: EdgeInsets.only(top: 5), + child: Text( + '${data.photoCount} Photo(s)', + style: GlobalStyle.productPrice, + ), + ), + Container(height: 8), + Container( + margin: EdgeInsets.only(top: 5), + child: Row( + children: [ + Icon( + Icons.store, + color: SOFT_GREY, + size: 20, + ), + Text( + ' ' + data.type_visite, + style: GlobalStyle.productName.copyWith( + fontSize: 13, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + + Widget buildHorizontalVisitListCard(context, VisitModel data) { final double imageWidth = (MediaQuery.of(context).size.width / 2.3); final double imageheight = (MediaQuery.of(context).size.width / 3.07); @@ -364,9 +468,15 @@ class _TabHomePageState extends State right: 4, // alignement à droite child: GestureDetector( onTap: () { - // TODO si visite validée ce n'est pas la meme url (on il mettre date validation dans le json frederik - SharedPrefs().urlMP4 = - '${ApiConstants.baseUrl}/MobilePortal4/index.html#ajax/visite_modification.html?visite=${data.id_visite}'; + // TODO: si visite validée ce n'est pas la meme url + if (data.date_validation == null) { + SharedPrefs().urlMP4 = + '${ApiConstants.baseUrl}/MobilePortal4/index.html#ajax/visite_modification.html?visite=${data.id_visite}'; + } else { + SharedPrefs().urlMP4 = + '${ApiConstants.baseUrl}/MobilePortal4/index.html#ajax/visite_modification.html?visite=${data.id_visite}'; + } + eventBus.fire(UrlEvent(SharedPrefs().urlMP4)); }, child: Image.asset( @@ -389,7 +499,10 @@ class _TabHomePageState extends State ), badgeContent: Text(data.photoCount.toString(), style: TextStyle(color: Colors.white)), - child: Icon(Icons.camera_alt_sharp), + child: Icon( + Icons.camera_alt_sharp, + color: Colors.grey, + ), ), ), ), diff --git a/lib/ui/sync/sync_calendar.dart b/lib/ui/sync/sync_calendar copy.dart similarity index 94% rename from lib/ui/sync/sync_calendar.dart rename to lib/ui/sync/sync_calendar copy.dart index 3caf655..7469812 100644 --- a/lib/ui/sync/sync_calendar.dart +++ b/lib/ui/sync/sync_calendar copy.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:mobdr/network/api_provider.dart'; -class SyncCalendarPage extends StatefulWidget { +class SyncCalendarPage2 extends StatefulWidget { @override - _SyncCalendarPageState createState() => _SyncCalendarPageState(); + _SyncCalendarPage2State createState() => _SyncCalendarPage2State(); } -class _SyncCalendarPageState extends State { +class _SyncCalendarPage2State extends State { bool _isSyncing = false; bool _syncSuccessful = false; String? _syncErrorMessage; diff --git a/lib/ui/sync/synchronization copy.dart b/lib/ui/sync/synchronization copy.dart new file mode 100644 index 0000000..08c1d46 --- /dev/null +++ b/lib/ui/sync/synchronization copy.dart @@ -0,0 +1,451 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:intl/intl.dart'; + +import 'package:mobdr/main.dart'; +import 'package:mobdr/config/constant.dart'; +import 'package:mobdr/config/global_style.dart'; +import 'package:mobdr/service/shared_prefs.dart'; +import 'package:mobdr/events.dart'; +import 'package:mobdr/model/visit_model.dart'; +import 'package:mobdr/ui/reusable/cache_image_network.dart'; +import 'package:mobdr/ui/sync/upload_photos.dart'; +import 'package:mobdr/ui/sync/check_connection.dart'; +import 'package:mobdr/ui/sync/synchronization.dart'; + +class SynchronizationPage2 extends StatefulWidget { + @override + _SynchronizationPage2State createState() => _SynchronizationPage2State(); +} + +class _SynchronizationPage2State extends State { + late List tosyncVisitData = []; + bool isLoading = true; + + // _listKey is used for AnimatedList + var _listKey = GlobalKey(); + + @override + void initState() { + super.initState(); + loadData(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final double boxImageSize = (MediaQuery.of(context).size.width / 4); + return Scaffold( + appBar: AppBar( + iconTheme: IconThemeData( + color: GlobalStyle.appBarIconThemeColor, + ), + elevation: GlobalStyle.appBarElevation, + title: Text( + 'Data synchronization', + style: GlobalStyle.appBarTitle, + ), + backgroundColor: GlobalStyle.appBarBackgroundColor, + systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle), + body: isLoading + ? Center( + child: CircularProgressIndicator(), + ) + : Column( + children: [ + Container( + padding: EdgeInsets.fromLTRB(16, 16, 16, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Calendar', style: GlobalStyle.horizontalTitle), + Row( + children: [ + Icon(Icons.refresh), + Text(SharedPrefs().lastCalendarRefresh.isNotEmpty + ? SharedPrefs().lastCalendarRefresh + : "Never"), + ], + ), + ], + ), + ), + SizedBox(height: 8), // Ajout de l'espace ici + TextButton( + onPressed: () async { + final result = await Navigator.push( + context, + MaterialPageRoute( + //builder: (context) => SyncCalendarPage()), + builder: (context) => SynchronizationPage2()), + ); + + // Refresh the widget if the synchronization was successful. + if (result == true) { + SharedPrefs().lastCalendarRefresh = + DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now()); + + eventBus.fire(RefreshCalendarEvent( + SharedPrefs().lastCalendarRefresh)); + + setState(() { + loadData(); + }); + } else + SharedPrefs().lastCalendarRefresh = ""; + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Synchronize', + style: TextStyle( + color: Colors.blue, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Icon(Icons.chevron_right, color: Colors.blue), + ], + ), + ), + Container( + padding: EdgeInsets.fromLTRB(16, 16, 16, 0), + child: Row( + children: [ + Text('Visits', style: GlobalStyle.horizontalTitle), + ], + ), + ), + if (tosyncVisitData.isEmpty) + Container( + padding: EdgeInsets.all(16), + child: Text("You didn't take any photos..."), + ), + Flexible( + child: AnimatedList( + key: _listKey, + initialItemCount: tosyncVisitData.length, + physics: AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index, animation) { + return Dismissible( + key: UniqueKey(), + direction: DismissDirection.endToStart, + onDismissed: (direction) { + // the photo must be removed + setState(() { + tosyncVisitData.removeAt(index); + _listKey = GlobalKey(); + }); + }, + background: Container( + color: Colors.red, + child: Stack( + children: [ + Positioned.fill( + child: Align( + alignment: Alignment.center, + child: Icon( + Icons.delete, + color: Colors.white, + ), + ), + ), + ], + ), + ), + child: _buildVisitlistCard(tosyncVisitData[index], + boxImageSize, animation, index), + ); + }, + ), + ), + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey, + offset: Offset(0.0, 1.0), //(x,y) + blurRadius: 2.0, + ), + ], + ), + child: Row( + children: [ + Container( + child: GestureDetector( + onTap: () { + // TODO filter functionality to be implemented (view deleted visits) + /*` + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatUsPage())); + */ + }, + child: ClipOval( + child: Container( + color: SOFT_BLUE, + padding: EdgeInsets.all(9), + child: Icon(Icons.filter_list, + color: Colors.white, size: 16)), + ), + ), + ), + SizedBox( + width: 10, + ), + Expanded( + child: GestureDetector( + onTap: tosyncVisitData.isNotEmpty + ? () { + navigateToPage(context, 0, 0); + } + : null, + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.fromLTRB(12, 8, 12, 8), + margin: EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + width: 1, + color: tosyncVisitData.isNotEmpty + ? Colors.red + : Colors.grey), + borderRadius: BorderRadius.all( + Radius.circular(10), + ), + ), + child: Text( + 'Synchronize ALL visits', + style: TextStyle( + color: tosyncVisitData.isNotEmpty + ? Colors.red + : Colors.grey, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildVisitlistCard(VisitModel data, 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: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(10)), + child: buildCacheNetworkImage( + width: boxImageSize, + height: boxImageSize, + url: data.image, + ), + ), + SizedBox( + width: 10, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(top: 5), + child: Row( + children: [ + Text(data.name, style: GlobalStyle.productPrice) + ], + ), + ), + Container(height: 8), + Text( + data.date, + style: GlobalStyle.productSale, + ), + Container( + margin: EdgeInsets.only(top: 5), + child: Text( + '${data.photoCount} Photo(s)', + style: GlobalStyle.productPrice, + ), + ), + Container(height: 8), + Container( + margin: EdgeInsets.only(top: 5), + child: Row( + children: [ + Icon( + Icons.store, + color: SOFT_GREY, + size: 20, + ), + Text( + ' ' + data.type_visite, + style: GlobalStyle.productName.copyWith( + fontSize: 13, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ) + ], + ), + ), + ], + ), + ) + ], + ), + Container( + margin: EdgeInsets.only(top: 12), + child: Row( + children: [ + Expanded( + child: (data.photoCount == 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: () {}, + child: Text( + 'Synchronize', + style: TextStyle( + color: Colors.grey[600], + fontWeight: FontWeight.bold, + fontSize: 13), + textAlign: TextAlign.center, + )) + : OutlinedButton( + onPressed: () { + navigateToPage( + context, index, data.id_visite); + }, + 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( + 'Synchronize', + style: TextStyle( + color: SOFT_BLUE, + fontWeight: FontWeight.bold, + fontSize: 13), + textAlign: TextAlign.center, + )), + ), + ], + ), + ) + ], + ), + ), + ), + ), + ); + } + + Future navigateToPage( + BuildContext context, int index, int id_visite) async { + var connectivityResult = await (Connectivity().checkConnectivity()); + + if (connectivityResult == ConnectivityResult.none) { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CheckConnectionPage( + redirectPage: UploadPhotosPage(pp_id_visite: id_visite), + ), + ), + ); + + // the visit must be removed + /* + setState(() { + tosyncVisitData.removeAt(index); + _listKey = GlobalKey(); + }); + */ + } else { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => UploadPhotosPage(pp_id_visite: id_visite))); + } + + /* + setState(() { + isLoading = true; // Mettez à jour l'état ici + }); + + // remove all items + int itemCount = tosyncVisitData.length; + for (int i = itemCount - 1; i >= 0; i--) { + tosyncVisitData.removeAt(i); + _listKey.currentState!.removeItem( + i, + (context, animation) => + Container()); // Remplacez Container() par le widget à animer lors de la suppression de l'élément + } + + loadData(); + */ + } + + /// Initializes data when the page loads. + Future loadData() async { + // initialization of data with all visits to be synchronized + tosyncVisitData = await VisitModel.getToSyncVisits(); + + setState(() { + isLoading = false; + }); + } +} diff --git a/lib/ui/sync/synchronization.dart b/lib/ui/sync/synchronization.dart index ae126fd..ea4512b 100644 --- a/lib/ui/sync/synchronization.dart +++ b/lib/ui/sync/synchronization.dart @@ -1,400 +1,211 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:intl/intl.dart'; - -import 'package:mobdr/main.dart'; -import 'package:mobdr/config/constant.dart'; -import 'package:mobdr/config/global_style.dart'; -import 'package:mobdr/service/shared_prefs.dart'; -import 'package:mobdr/events.dart'; -import 'package:mobdr/model/visit_model.dart'; -import 'package:mobdr/ui/reusable/cache_image_network.dart'; -import 'package:mobdr/ui/sync/upload_photos.dart'; -import 'package:mobdr/ui/sync/check_connection.dart'; -import 'package:mobdr/ui/sync/sync_calendar.dart'; - -class SynchronizationPage extends StatefulWidget { - @override - _SynchronizationPageState createState() => _SynchronizationPageState(); -} - -class _SynchronizationPageState extends State { - late List tosyncVisitData = []; - - // _listKey is used for AnimatedList - var _listKey = GlobalKey(); - - @override - void initState() { - super.initState(); - - loadData(); - } - - @override - void dispose() { - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final double boxImageSize = (MediaQuery.of(context).size.width / 4); - return Scaffold( - appBar: AppBar( - iconTheme: IconThemeData( - color: GlobalStyle.appBarIconThemeColor, - ), - elevation: GlobalStyle.appBarElevation, - title: Text( - 'Data synchronization', - style: GlobalStyle.appBarTitle, - ), - backgroundColor: GlobalStyle.appBarBackgroundColor, - systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle), - body: Column( - children: [ - Container( - padding: EdgeInsets.fromLTRB(16, 16, 16, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Calendar', style: GlobalStyle.horizontalTitle), - Row( - children: [ - Icon(Icons.refresh), - Text(SharedPrefs().lastCalendarRefresh.isNotEmpty - ? SharedPrefs().lastCalendarRefresh - : "Never"), - ], - ), - ], - ), - ), - SizedBox(height: 8), // Ajout de l'espace ici - ElevatedButton( - onPressed: () async { - final result = await Navigator.push( - context, - MaterialPageRoute(builder: (context) => SyncCalendarPage()), - ); - - // Refresh the widget if the synchronization was successful. - if (result == true) { - SharedPrefs().lastCalendarRefresh = - DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now()); - - eventBus.fire( - RefreshCalendarEvent(SharedPrefs().lastCalendarRefresh)); - - setState(() { - loadData(); - }); - } else - SharedPrefs().lastCalendarRefresh = ""; - }, - child: Text('Synchroniser'), - ), - Container( - padding: EdgeInsets.fromLTRB(16, 16, 16, 0), - child: Row( - children: [ - Text('Visits', style: GlobalStyle.horizontalTitle), - ], - ), - ), - if (tosyncVisitData.isEmpty) - Container( - padding: EdgeInsets.all(16), - child: Text("You didn't take any photos..."), - ), - Flexible( - child: AnimatedList( - key: _listKey, - initialItemCount: tosyncVisitData.length, - physics: AlwaysScrollableScrollPhysics(), - itemBuilder: (context, index, animation) { - return Dismissible( - key: UniqueKey(), - direction: DismissDirection.endToStart, - onDismissed: (direction) { - // the photo must be removed - setState(() { - tosyncVisitData.removeAt(index); - _listKey = GlobalKey(); - }); - }, - background: Container( - color: Colors.red, - child: Stack( - children: [ - Positioned.fill( - child: Align( - alignment: Alignment.center, - child: Icon( - Icons.delete, - color: Colors.white, - ), - ), - ), - ], - ), - ), - child: _buildVisitlistCard( - tosyncVisitData[index], boxImageSize, animation, index), - ); - }, - ), - ), - Container( - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey, - offset: Offset(0.0, 1.0), //(x,y) - blurRadius: 2.0, - ), - ], - ), - child: Row( - children: [ - Container( - child: GestureDetector( - onTap: () { - // TODO functionality to be implemented - /*` - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChatUsPage())); - */ - }, - child: ClipOval( - child: Container( - color: SOFT_BLUE, - padding: EdgeInsets.all(9), - child: Icon(Icons.filter_list, - color: Colors.white, size: 16)), - ), - ), - ), - SizedBox( - width: 10, - ), - Expanded( - child: GestureDetector( - onTap: tosyncVisitData.isNotEmpty - ? () { - navigateToPage(context, 0); - } - : null, - child: Container( - alignment: Alignment.center, - padding: EdgeInsets.fromLTRB(12, 8, 12, 8), - margin: EdgeInsets.only(right: 8), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all( - width: 1, - color: tosyncVisitData.isNotEmpty - ? Colors.red - : Colors.grey), - borderRadius: BorderRadius.all( - Radius.circular(10), - ), - ), - child: Text( - 'Synchronize ALL visits', - style: TextStyle( - color: tosyncVisitData.isNotEmpty - ? Colors.red - : Colors.grey, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildVisitlistCard(VisitModel data, 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: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(10)), - child: buildCacheNetworkImage( - width: boxImageSize, - height: boxImageSize, - url: data.image, - ), - ), - SizedBox( - width: 10, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: EdgeInsets.only(top: 5), - child: Row( - children: [ - Text(data.name, style: GlobalStyle.productPrice) - ], - ), - ), - Container(height: 8), - Text( - data.date, - style: GlobalStyle.productSale, - ), - Container( - margin: EdgeInsets.only(top: 5), - child: Text( - '${data.photoCount} Photo(s)', - style: GlobalStyle.productPrice, - ), - ), - Container(height: 8), - Container( - margin: EdgeInsets.only(top: 5), - child: Row( - children: [ - Icon( - Icons.store, - color: SOFT_GREY, - size: 20, - ), - Text( - ' ' + data.type_visite, - style: GlobalStyle.productName.copyWith( - fontSize: 13, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ) - ], - ), - ), - ], - ), - ) - ], - ), - Container( - margin: EdgeInsets.only(top: 12), - child: Row( - children: [ - Expanded( - child: (data.photoCount == 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: () {}, - child: Text( - 'Synchronize', - style: TextStyle( - color: Colors.grey[600], - fontWeight: FontWeight.bold, - fontSize: 13), - textAlign: TextAlign.center, - )) - : OutlinedButton( - onPressed: () { - navigateToPage(context, data.id_visite); - }, - 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( - 'Synchronize', - style: TextStyle( - color: SOFT_BLUE, - fontWeight: FontWeight.bold, - fontSize: 13), - textAlign: TextAlign.center, - )), - ), - ], - ), - ) - ], - ), - ), - ), - ), - ); - } - - Future navigateToPage(BuildContext context, int id_visite) async { - var connectivityResult = await (Connectivity().checkConnectivity()); - - if (connectivityResult == ConnectivityResult.none) { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => CheckConnectionPage( - redirectPage: UploadPhotosPage(pp_id_visite: id_visite), - ), - ), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => UploadPhotosPage(pp_id_visite: id_visite))); - } - } - - /// Initializes data when the page loads. - void loadData() { - // initialization of data with all visits to be synchronized - tosyncVisitData = VisitModel.getToSyncVisits(); - } -} +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:mobdr/service/shared_prefs.dart'; + +class SynchronizationPage extends StatefulWidget { + @override + _SynchronizationPageState createState() => _SynchronizationPageState(); +} + +class _SynchronizationPageState extends State + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _rotationAnimation; + + bool _isSyncing = false; + bool _syncCompleted = false; + bool _backofficeSyncCompleted = false; + bool _photosSyncCompleted = false; + bool _logSyncCompleted = false; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: Duration(seconds: 2), + ); + _rotationAnimation = + Tween(begin: 0, end: 1).animate(_animationController); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + void startSync() { + setState(() { + _isSyncing = true; + _syncCompleted = false; + _backofficeSyncCompleted = false; + _photosSyncCompleted = false; + _logSyncCompleted = false; + }); + + _animationController.repeat(); + + // Simulation d'une tâche de synchronisation + Future.delayed(const Duration(seconds: 3), () { + SharedPrefs().lastCalendarRefresh = + DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now()); + setState(() { + _isSyncing = false; + _syncCompleted = true; + _backofficeSyncCompleted = true; + _photosSyncCompleted = false; + _logSyncCompleted = false; + }); + _animationController.stop(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Data synchronization'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 20), + Center( + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.blue, + ), + child: IconButton( + icon: AnimatedBuilder( + animation: _animationController, + builder: (BuildContext context, Widget? child) { + return Transform.rotate( + angle: _rotationAnimation.value * 2.0 * 3.14, + child: Icon( + Icons.refresh, + color: Colors.white, + size: 80, + ), + ); + }, + ), + onPressed: _isSyncing ? null : startSync, + ), + ), + ), + SizedBox(height: 20), + Text( + SharedPrefs().lastCalendarRefresh.isNotEmpty + ? SharedPrefs().lastCalendarRefresh + : "Never", + style: TextStyle(fontSize: 16), + textAlign: TextAlign.center, + ), + SizedBox(height: 40), + SyncItem( + icon: Icons.business, + title: 'Backoffice', + description: 'Synchronisation des données du backoffice', + isSyncing: _isSyncing, + syncCompleted: _syncCompleted, + isError: _syncCompleted && !_backofficeSyncCompleted, + ), + SizedBox(height: 20), + SyncItem( + icon: Icons.photo, + title: 'Photos', + description: 'Synchronisation des photos', + isSyncing: _isSyncing, + syncCompleted: _syncCompleted, + isError: _syncCompleted && !_photosSyncCompleted, + ), + SizedBox(height: 20), + SyncItem( + icon: Icons.warning, + title: 'Log', + description: 'Synchronisation des journaux d\'activité', + isSyncing: _isSyncing, + syncCompleted: _syncCompleted, + isError: _syncCompleted && !_logSyncCompleted, + ), + ], + ), + ), + ); + } +} + +class SyncItem extends StatelessWidget { + final IconData icon; + final String title; + final String description; + final bool isSyncing; + final bool syncCompleted; + final bool isError; + + const SyncItem({ + required this.icon, + required this.title, + required this.description, + required this.isSyncing, + required this.syncCompleted, + required this.isError, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + icon, + color: Colors.blue, + size: 24, + ), + SizedBox(width: 10), + Text( + title, + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ], + ), + SizedBox(height: 10), + Text(description), + SizedBox(height: 10), + if (isSyncing) + LinearProgressIndicator( + backgroundColor: Colors.grey[300], + valueColor: AlwaysStoppedAnimation(Colors.blue), + ), + if (syncCompleted) + Row( + children: [ + Icon( + isError ? Icons.error_outline : Icons.check, + color: isError ? Colors.red : Colors.green, + size: 20, + ), + SizedBox(width: 10), + Text( + isError + ? 'Erreur de synchronisation' + : 'Synchronisation terminée', + style: TextStyle( + color: isError ? Colors.red : Colors.green, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/ui/sync/testsync copy.dart b/lib/ui/sync/testsync copy.dart new file mode 100644 index 0000000..45a04d9 --- /dev/null +++ b/lib/ui/sync/testsync copy.dart @@ -0,0 +1,228 @@ +import 'package:flutter/material.dart'; + +import 'package:mobdr/service/shared_prefs.dart'; +import 'package:mobdr/config/global_style.dart'; + +class SyncPage extends StatefulWidget { + @override + _SyncPageState createState() => _SyncPageState(); +} + +class _SyncPageState extends State + with SingleTickerProviderStateMixin { + AnimationController? _animationController; + late Animation _rotationAnimation; + + bool _isSyncing = false; + bool _syncCompleted = false; + + @override + void initState() { + super.initState(); + + _animationController = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + ); + + _rotationAnimation = Tween( + begin: 0, + end: 1, + ).animate(_animationController!); + + _animationController!.addStatusListener((status) { + if (status == AnimationStatus.completed) { + setState(() { + _syncCompleted = true; + }); + } + }); + + // Lancement automatique de la synchronisation au démarrage + //startSync(); + } + + @override + void dispose() { + _animationController!.dispose(); + super.dispose(); + } + + void startSync() { + setState(() { + _isSyncing = true; + _syncCompleted = false; + }); + + _animationController!.repeat(); + + // Simulation d'une tâche de synchronisation + Future.delayed(const Duration(seconds: 3), () { + setState(() { + _isSyncing = false; + _syncCompleted = true; + }); + _animationController!.stop(); + }); + } + + @override + Widget build(BuildContext context) { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + + // Calcul du positionnement du premier cercle + final double circleSize = 55; + final double circleLeft = (screenWidth - circleSize) / 2 - 23; + final double circleTop = (screenHeight - circleSize) / 2 + 30; + + // Calcul du positionnement du deuxième cercle + final double secondCircleSize = 29; + final double secondCircleLeft = (screenWidth - circleSize) / 2 + 142; + final double secondCircleTop = (screenHeight - circleSize) / 2 + 75; + + // Définition de la couleur du premier cercle en fonction de l'état de synchronisation + Color circleColor; + if (_isSyncing) { + circleColor = Colors.lightBlue; + } else if (_syncCompleted) { + circleColor = Colors.green; + } else { + circleColor = Colors.transparent; + } + + return Scaffold( + appBar: AppBar( + title: Text('Page de synchronisation'), + ), + body: Stack( + children: [ + Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/synchro.png'), + fit: BoxFit.contain, + ), + ), + ), + Container( + padding: EdgeInsets.fromLTRB(16, 16, 16, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Calendar', style: GlobalStyle.horizontalTitle), + Row( + children: [ + Icon(Icons.refresh), + Text(SharedPrefs().lastCalendarRefresh.isNotEmpty + ? SharedPrefs().lastCalendarRefresh + : "Never"), + ], + ), + ], + ), + ), + if (_isSyncing || _syncCompleted) + Positioned( + left: circleLeft, + top: circleTop, + child: Container( + width: circleSize, + height: circleSize, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: circleColor, + ), + child: _syncCompleted + ? Icon( + Icons.check, + color: Colors.white, + size: circleSize - 20, + ) + : null, + ), + ), + if (_isSyncing || _syncCompleted) + Positioned( + left: secondCircleLeft, + top: secondCircleTop, + child: Container( + width: secondCircleSize, + height: secondCircleSize, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: circleColor, + ), + child: _syncCompleted + ? Icon( + Icons.check, + color: Colors.white, + size: secondCircleSize - 15, + ) + : null, + ), + ), + if (_isSyncing) + Positioned( + left: circleLeft + (circleSize - 70) / 2, + top: circleTop + (circleSize - 70) / 2, + child: Align( + alignment: Alignment.center, + child: AnimatedBuilder( + animation: _animationController!, + builder: (BuildContext context, Widget? child) { + return Transform.rotate( + angle: _rotationAnimation.value * 2.0 * 3.14, + child: Icon( + Icons.refresh, + color: Colors.white, + size: 70, + ), + ); + }, + ), + ), + ), + if (_isSyncing) + Positioned( + left: secondCircleLeft + (secondCircleSize - 35) / 2, + top: secondCircleTop + (secondCircleSize - 34) / 2, + child: AnimatedBuilder( + animation: _animationController!, + builder: (BuildContext context, Widget? child) { + return Transform.rotate( + angle: _rotationAnimation.value * 2.0 * 3.14, + child: Icon( + Icons.refresh, + color: Colors.white, + size: 35, + ), + ); + }, + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox(height: 20), + if (_isSyncing) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: LinearProgressIndicator(), + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: _isSyncing ? null : startSync, + child: Text('Lancer la synchronisation'), + ), + SizedBox(height: 20), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/ui/sync/upload_photos.dart b/lib/ui/sync/upload_photos.dart index c6a5002..1d2eb2e 100644 --- a/lib/ui/sync/upload_photos.dart +++ b/lib/ui/sync/upload_photos.dart @@ -5,6 +5,7 @@ import 'package:mobdr/main.dart'; import 'package:mobdr/events.dart'; import 'package:mobdr/db/box_visit_photo.dart'; import 'package:mobdr/network/api_provider.dart'; +import 'package:mobdr/service/shared_prefs.dart'; class UploadPhotosPage extends StatefulWidget { final int pp_id_visite; @@ -16,12 +17,12 @@ class UploadPhotosPage extends StatefulWidget { } class _UploadPhotosPageState extends State { - bool _isUploading = false; - bool _isFinished = false; + bool _isSyncing = false; + bool _syncSuccessful = false; int _totalUploaded = 0; int _totalPhotos = 0; - late List _photosList; + late List _visitPhotosList; ApiProvider _apiProvider = ApiProvider(); @@ -29,25 +30,17 @@ class _UploadPhotosPageState extends State { void initState() { super.initState(); - // "visit" mode - if (widget.pp_id_visite > 0) { - _photosList = objectbox.getAllVisitPhotosByVisit(widget.pp_id_visite); - _totalPhotos = _photosList.length; - // "all" mode - } else { - _photosList = objectbox.getAllVisitPhotos(); - _totalPhotos = _photosList.length; - } + loadData(); } - void _uploadPhotos() async { + Future _uploadVisitPhotos() async { setState(() { - _isUploading = true; - _isFinished = false; + _isSyncing = true; + _syncSuccessful = false; }); // parse all photos - for (var photo in _photosList) { + for (var photo in _visitPhotosList) { int id_photo_mp4 = -1; bool isUpdatePhotoTypologie = true; bool isUpdatePhotoVisibility = true; @@ -119,7 +112,8 @@ class _UploadPhotosPageState extends State { } // Get unique id_visite values from _photosList - Set uniqueIds = _photosList.map((photo) => photo.id_visite).toSet(); + Set uniqueIds = + _visitPhotosList.map((photo) => photo.id_visite).toSet(); // Send VisitPhotoCountEvent for each unique id_visite for (int id_visite in uniqueIds) { @@ -128,11 +122,45 @@ class _UploadPhotosPageState extends State { } setState(() { - _isUploading = false; - _isFinished = true; + _isSyncing = false; + _syncSuccessful = true; }); } + Future _clearVisitPhotoCache() async { + List _visitPhotoListTokeep; + Directory photosDir = Directory(SharedPrefs().photosDir); + + _visitPhotoListTokeep = objectbox.getAllVisitPhotos(); + + // Get a list of all files in the "photos" directory + final List files = await photosDir.list().toList(); + + // Check each file in the directory + for (FileSystemEntity file in files) { + if (file is File) { + // Extract the file name from the file path + String fileName = file.path.split('/').last; + + // Check if the file exists in the _visitPhotoListTokeep + bool existsInList = _visitPhotoListTokeep + .any((visitPhoto) => visitPhoto.image_name == fileName); + + if (!existsInList) { + // Delete the file if it doesn't exist in the _visitPhotoListTokeep + await file.delete(); + print('Deleted file: $fileName'); + } + } + } + } + + void _popScreen() { + if (mounted) { + Navigator.of(context).pop(_syncSuccessful); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -160,7 +188,7 @@ class _UploadPhotosPageState extends State { children: [ Center( child: Visibility( - visible: _isUploading, + visible: _isSyncing, child: Container( height: 150, width: 150, @@ -175,7 +203,7 @@ class _UploadPhotosPageState extends State { ), Center( child: Visibility( - visible: !_isUploading && _totalUploaded == _totalPhotos, + visible: !_isSyncing && _totalUploaded == _totalPhotos, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -195,7 +223,7 @@ class _UploadPhotosPageState extends State { ), Center( child: Visibility( - visible: !_isUploading && _totalUploaded != _totalPhotos, + visible: !_isSyncing && _totalUploaded != _totalPhotos, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -216,17 +244,36 @@ class _UploadPhotosPageState extends State { ], ), ), + if (_syncSuccessful) + Column( + children: [ + Icon(Icons.check_circle, size: 100.0, color: Colors.green), + SizedBox(height: 16.0), + ElevatedButton( + onPressed: _popScreen, + child: Text('Synchronisation réussie'), + ), + ], + ), SizedBox(height: 16), Visibility( - visible: !_isUploading, + visible: !_isSyncing, child: ElevatedButton( child: Text('Upload Photos ($_totalUploaded / $_totalPhotos)'), - onPressed: _isFinished + onPressed: _syncSuccessful ? null - : () { - _uploadPhotos(); + : () async { + // upload photo to server + await _uploadVisitPhotos(); + + // deletes photos that are no longer in any visits + await _clearVisitPhotoCache(); + + // go back + Navigator.pop(context); } + //, ), ), @@ -235,4 +282,18 @@ class _UploadPhotosPageState extends State { ), ); } + + /// Initializes data when the page loads. + void loadData() { + // "visit" mode + if (widget.pp_id_visite > 0) { + _visitPhotosList = + objectbox.getAllVisitPhotosByVisit(widget.pp_id_visite); + _totalPhotos = _visitPhotosList.length; + // "all" mode + } else { + _visitPhotosList = objectbox.getAllVisitPhotos(); + _totalPhotos = _visitPhotosList.length; + } + } } diff --git a/lib/ui/visit/visit_photo_typology_list.dart b/lib/ui/visit/visit_photo_typology_list.dart index 583010d..51d154f 100644 --- a/lib/ui/visit/visit_photo_typology_list.dart +++ b/lib/ui/visit/visit_photo_typology_list.dart @@ -140,11 +140,6 @@ class _VisitPhotoTypologyListPageState child: GestureDetector( onTap: () { ImportImageFromGallery(); - /*Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PhotoPickPage())); - */ }, child: ClipOval( child: Container( diff --git a/pubspec.yaml b/pubspec.yaml index a019ecd..aafa676 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -162,6 +162,7 @@ flutter: - assets/images/logo_dark.png - assets/images/logo_mp4.png - assets/images/logo_horizontal.png + - assets/images/synchro.png - assets/images/onboarding/search_product.gif - assets/images/process_timeline/status1.png - assets/images/process_timeline/status2.png