refactor:synchronization (step 1)
parent
fe798b6ae2
commit
06b33e9ec6
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
|
|
@ -178,7 +178,7 @@ class GlobalStyle {
|
||||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold);
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold);
|
||||||
|
|
||||||
static const TextStyle cardTitle =
|
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(
|
static const TextStyle textPromo = TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,10 @@ class VisitModel {
|
||||||
late String image;
|
late String image;
|
||||||
late String type_visite;
|
late String type_visite;
|
||||||
late String langage;
|
late String langage;
|
||||||
|
late DateTime? date_validation;
|
||||||
|
|
||||||
VisitModel(
|
VisitModel({
|
||||||
{required this.id,
|
required this.id,
|
||||||
required this.id_distrib,
|
required this.id_distrib,
|
||||||
required this.id_etab,
|
required this.id_etab,
|
||||||
required this.id_visite,
|
required this.id_visite,
|
||||||
|
|
@ -23,7 +24,9 @@ class VisitModel {
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.image,
|
required this.image,
|
||||||
required this.type_visite,
|
required this.type_visite,
|
||||||
required this.langage});
|
required this.langage,
|
||||||
|
required this.date_validation,
|
||||||
|
});
|
||||||
|
|
||||||
static Future<List<VisitModel>> getTodayVisits() async {
|
static Future<List<VisitModel>> getTodayVisits() async {
|
||||||
// Retrieve all today visits from the database
|
// Retrieve all today visits from the database
|
||||||
|
|
@ -42,7 +45,8 @@ class VisitModel {
|
||||||
.format(visite.date_visite),
|
.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,
|
||||||
|
date_validation: visite.date_validation))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// Return the list of VisiteModel
|
// Return the list of VisiteModel
|
||||||
|
|
@ -66,16 +70,17 @@ class VisitModel {
|
||||||
.format(visite.date_visite),
|
.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,
|
||||||
|
date_validation: visite.date_validation))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// Return the list of VisiteModel
|
// Return the list of VisiteModel
|
||||||
return visitModelList;
|
return visitModelList;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<VisitModel> getToSyncVisits() {
|
static Future<List<VisitModel>> getToSyncVisits() async {
|
||||||
// Retrieve all visits from the database
|
// Retrieve all visits from the database
|
||||||
final visits = objectbox.getAllVisit();
|
final visits = await objectbox.getAllVisit();
|
||||||
|
|
||||||
// Map each retrieved visit to VisiteModel
|
// Map each retrieved visit to VisiteModel
|
||||||
final visitModelList = visits
|
final visitModelList = visits
|
||||||
|
|
@ -90,7 +95,8 @@ class VisitModel {
|
||||||
.format(visite.date_visite),
|
.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,
|
||||||
|
date_validation: visite.date_validation))
|
||||||
.where((visit) =>
|
.where((visit) =>
|
||||||
visit.photoCount >
|
visit.photoCount >
|
||||||
0) // Filter out visits with photoCount equal to 0
|
0) // Filter out visits with photoCount equal to 0
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,6 @@ class ObjectBox {
|
||||||
.map((query) => query.find());
|
.map((query) => query.find());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _putNotesInTx(Store store, List<Note> notes) =>
|
|
||||||
store.box<Note>().putMany(notes);
|
|
||||||
|
|
||||||
/// Add a note within a transaction.
|
/// Add a note within a transaction.
|
||||||
///
|
///
|
||||||
/// To avoid frame drops, run ObjectBox operations that take longer than a
|
/// To avoid frame drops, run ObjectBox operations that take longer than a
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import 'package:mobdr/config/global_style.dart';
|
||||||
import 'package:mobdr/ui/reusable/reusable_widget.dart';
|
import 'package:mobdr/ui/reusable/reusable_widget.dart';
|
||||||
import 'package:mobdr/network/api_provider.dart';
|
import 'package:mobdr/network/api_provider.dart';
|
||||||
import 'package:mobdr/main.dart';
|
import 'package:mobdr/main.dart';
|
||||||
import 'package:mobdr/ui/home/tab_home.dart';
|
|
||||||
|
|
||||||
const completeColor = Color(0xff5e6172);
|
const completeColor = Color(0xff5e6172);
|
||||||
const inProgressColor = Color(0xff5ec792);
|
const inProgressColor = Color(0xff5ec792);
|
||||||
|
|
|
||||||
|
|
@ -77,38 +77,44 @@ class _HomePageState extends State<HomePage>
|
||||||
type: BottomNavigationBarType.fixed,
|
type: BottomNavigationBarType.fixed,
|
||||||
currentIndex: _currentIndex,
|
currentIndex: _currentIndex,
|
||||||
onTap: (value) {
|
onTap: (value) {
|
||||||
|
setState(() {
|
||||||
_currentIndex = value;
|
_currentIndex = value;
|
||||||
_pageController.jumpToPage(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();
|
FocusScope.of(context).unfocus();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
selectedFontSize: 8,
|
selectedFontSize: 8,
|
||||||
unselectedFontSize: 8,
|
unselectedFontSize: 8,
|
||||||
iconSize: 28,
|
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(
|
selectedLabelStyle: TextStyle(
|
||||||
color: _currentIndex == 1 ? ASSENT_COLOR : PRIMARY_COLOR,
|
color: Colors.blue, // Couleur du texte de l'onglet sélectionné
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold,
|
||||||
unselectedLabelStyle:
|
),
|
||||||
TextStyle(color: CHARCOAL, fontWeight: FontWeight.bold),
|
unselectedLabelStyle: TextStyle(
|
||||||
selectedItemColor: _currentIndex == 1 ? ASSENT_COLOR : PRIMARY_COLOR,
|
color: CHARCOAL, // Couleur du texte des onglets non sélectionnés
|
||||||
unselectedItemColor: CHARCOAL,
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
items: [
|
items: [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
label: 'Home',
|
label: 'Home',
|
||||||
icon: Icon(Icons.home,
|
icon: Icon(Icons.home),
|
||||||
color: _currentIndex == 0 ? PRIMARY_COLOR : CHARCOAL)),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
label: 'Synchro',
|
label: 'Synchro',
|
||||||
icon: Icon(Icons.sync,
|
icon: Icon(Icons.sync),
|
||||||
color: _currentIndex == 1 ? ASSENT_COLOR : CHARCOAL)),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
label: 'MP4',
|
label: 'MP4',
|
||||||
icon: Icon(Icons.web,
|
icon: Icon(Icons.web),
|
||||||
color: _currentIndex == 2 ? PRIMARY_COLOR : CHARCOAL)),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
label: 'Account',
|
label: 'Account',
|
||||||
icon: Icon(Icons.person_outline,
|
icon: Icon(Icons.person_outline),
|
||||||
color: _currentIndex == 3 ? PRIMARY_COLOR : CHARCOAL)),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import 'package:mobdr/cubit/language/app_localizations.dart';
|
||||||
import 'package:mobdr/service/shared_prefs.dart';
|
import 'package:mobdr/service/shared_prefs.dart';
|
||||||
import 'package:mobdr/config/global_style.dart';
|
import 'package:mobdr/config/global_style.dart';
|
||||||
import 'package:mobdr/events.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/model/visit_model.dart';
|
||||||
import 'package:mobdr/ui/visit/visit_photo_typology.dart';
|
import 'package:mobdr/ui/visit/visit_photo_typology.dart';
|
||||||
import 'package:mobdr/ui/general/chat_us.dart';
|
import 'package:mobdr/ui/general/chat_us.dart';
|
||||||
|
|
@ -188,7 +188,7 @@ class _TabHomePageState extends State<TabHomePage>
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.fromLTRB(16, 16, 16, 0),
|
padding: EdgeInsets.fromLTRB(16, 8, 0, 0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text('Last visit access', style: GlobalStyle.horizontalTitle),
|
Text('Last visit access', style: GlobalStyle.horizontalTitle),
|
||||||
|
|
@ -197,10 +197,9 @@ class _TabHomePageState extends State<TabHomePage>
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(top: 8),
|
margin: EdgeInsets.only(top: 8),
|
||||||
height: boxImageSize * GlobalStyle.cardHeightMultiplication,
|
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: buildHorizontalVisitListCard(context, lastVisitData)),
|
child: buildVisitListCard(context, lastVisitData!)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -238,13 +237,12 @@ class _TabHomePageState extends State<TabHomePage>
|
||||||
Column(
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 16), // Ajout de l'espace ici
|
TextButton(
|
||||||
Text('Aucune visite ce jour'),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await Navigator.push(
|
final result = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => SyncCalendarPage()),
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SynchronizationPage()),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Refresh the widget if the synchronization was successful.
|
// Refresh the widget if the synchronization was successful.
|
||||||
|
|
@ -254,7 +252,20 @@ class _TabHomePageState extends State<TabHomePage>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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<TabHomePage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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: <Widget>[
|
||||||
|
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 imageWidth = (MediaQuery.of(context).size.width / 2.3);
|
||||||
final double imageheight = (MediaQuery.of(context).size.width / 3.07);
|
final double imageheight = (MediaQuery.of(context).size.width / 3.07);
|
||||||
|
|
||||||
|
|
@ -364,9 +468,15 @@ class _TabHomePageState extends State<TabHomePage>
|
||||||
right: 4, // alignement à droite
|
right: 4, // alignement à droite
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO si visite validée ce n'est pas la meme url (on il mettre date validation dans le json frederik
|
// TODO: si visite validée ce n'est pas la meme url
|
||||||
|
if (data.date_validation == null) {
|
||||||
SharedPrefs().urlMP4 =
|
SharedPrefs().urlMP4 =
|
||||||
'${ApiConstants.baseUrl}/MobilePortal4/index.html#ajax/visite_modification.html?visite=${data.id_visite}';
|
'${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));
|
eventBus.fire(UrlEvent(SharedPrefs().urlMP4));
|
||||||
},
|
},
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
|
|
@ -389,7 +499,10 @@ class _TabHomePageState extends State<TabHomePage>
|
||||||
),
|
),
|
||||||
badgeContent: Text(data.photoCount.toString(),
|
badgeContent: Text(data.photoCount.toString(),
|
||||||
style: TextStyle(color: Colors.white)),
|
style: TextStyle(color: Colors.white)),
|
||||||
child: Icon(Icons.camera_alt_sharp),
|
child: Icon(
|
||||||
|
Icons.camera_alt_sharp,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:mobdr/network/api_provider.dart';
|
import 'package:mobdr/network/api_provider.dart';
|
||||||
|
|
||||||
class SyncCalendarPage extends StatefulWidget {
|
class SyncCalendarPage2 extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_SyncCalendarPageState createState() => _SyncCalendarPageState();
|
_SyncCalendarPage2State createState() => _SyncCalendarPage2State();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SyncCalendarPageState extends State<SyncCalendarPage> {
|
class _SyncCalendarPage2State extends State<SyncCalendarPage2> {
|
||||||
bool _isSyncing = false;
|
bool _isSyncing = false;
|
||||||
bool _syncSuccessful = false;
|
bool _syncSuccessful = false;
|
||||||
String? _syncErrorMessage;
|
String? _syncErrorMessage;
|
||||||
|
|
@ -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<SynchronizationPage2> {
|
||||||
|
late List<VisitModel> tosyncVisitData = [];
|
||||||
|
bool isLoading = true;
|
||||||
|
|
||||||
|
// _listKey is used for AnimatedList
|
||||||
|
var _listKey = GlobalKey<AnimatedListState>();
|
||||||
|
|
||||||
|
@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<AnimatedListState>();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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: <Widget>[
|
||||||
|
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<Color>(
|
||||||
|
(Set<MaterialState> 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<void> 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<AnimatedListState>();
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
} 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<void> loadData() async {
|
||||||
|
// initialization of data with all visits to be synchronized
|
||||||
|
tosyncVisitData = await VisitModel.getToSyncVisits();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,400 +1,211 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
||||||
import 'package:intl/intl.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/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 {
|
class SynchronizationPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_SynchronizationPageState createState() => _SynchronizationPageState();
|
_SynchronizationPageState createState() => _SynchronizationPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SynchronizationPageState extends State<SynchronizationPage> {
|
class _SynchronizationPageState extends State<SynchronizationPage>
|
||||||
late List<VisitModel> tosyncVisitData = [];
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animationController;
|
||||||
|
late Animation<double> _rotationAnimation;
|
||||||
|
|
||||||
// _listKey is used for AnimatedList
|
bool _isSyncing = false;
|
||||||
var _listKey = GlobalKey<AnimatedListState>();
|
bool _syncCompleted = false;
|
||||||
|
bool _backofficeSyncCompleted = false;
|
||||||
|
bool _photosSyncCompleted = false;
|
||||||
|
bool _logSyncCompleted = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
loadData();
|
vsync: this,
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
_rotationAnimation =
|
||||||
|
Tween<double>(begin: 0, end: 1).animate(_animationController);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
super.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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final double boxImageSize = (MediaQuery.of(context).size.width / 4);
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
iconTheme: IconThemeData(
|
title: Text('Data synchronization'),
|
||||||
color: GlobalStyle.appBarIconThemeColor,
|
|
||||||
),
|
),
|
||||||
elevation: GlobalStyle.appBarElevation,
|
body: Padding(
|
||||||
title: Text(
|
padding: const EdgeInsets.all(16.0),
|
||||||
'Data synchronization',
|
child: Column(
|
||||||
style: GlobalStyle.appBarTitle,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
),
|
|
||||||
backgroundColor: GlobalStyle.appBarBackgroundColor,
|
|
||||||
systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle),
|
|
||||||
body: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Container(
|
SizedBox(height: 20),
|
||||||
padding: EdgeInsets.fromLTRB(16, 16, 16, 0),
|
Center(
|
||||||
child: Row(
|
child: Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
width: 100,
|
||||||
children: [
|
height: 100,
|
||||||
Text('Calendar', style: GlobalStyle.horizontalTitle),
|
decoration: BoxDecoration(
|
||||||
Row(
|
shape: BoxShape.circle,
|
||||||
children: [
|
color: Colors.blue,
|
||||||
Icon(Icons.refresh),
|
|
||||||
Text(SharedPrefs().lastCalendarRefresh.isNotEmpty
|
|
||||||
? SharedPrefs().lastCalendarRefresh
|
|
||||||
: "Never"),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
child: IconButton(
|
||||||
),
|
icon: AnimatedBuilder(
|
||||||
),
|
animation: _animationController,
|
||||||
SizedBox(height: 8), // Ajout de l'espace ici
|
builder: (BuildContext context, Widget? child) {
|
||||||
ElevatedButton(
|
return Transform.rotate(
|
||||||
onPressed: () async {
|
angle: _rotationAnimation.value * 2.0 * 3.14,
|
||||||
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<AnimatedListState>();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
background: Container(
|
|
||||||
color: Colors.red,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.delete,
|
Icons.refresh,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
size: 80,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: _buildVisitlistCard(
|
|
||||||
tosyncVisitData[index], boxImageSize, animation, index),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
onPressed: _isSyncing ? null : startSync,
|
||||||
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(
|
SizedBox(height: 20),
|
||||||
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: <Widget>[
|
|
||||||
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(
|
Text(
|
||||||
data.date,
|
SharedPrefs().lastCalendarRefresh.isNotEmpty
|
||||||
style: GlobalStyle.productSale,
|
? SharedPrefs().lastCalendarRefresh
|
||||||
),
|
: "Never",
|
||||||
Container(
|
style: TextStyle(fontSize: 16),
|
||||||
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<Color>(
|
|
||||||
(Set<MaterialState> 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,
|
textAlign: TextAlign.center,
|
||||||
))
|
),
|
||||||
: OutlinedButton(
|
SizedBox(height: 40),
|
||||||
onPressed: () {
|
SyncItem(
|
||||||
navigateToPage(context, data.id_visite);
|
icon: Icons.business,
|
||||||
},
|
title: 'Backoffice',
|
||||||
style: ButtonStyle(
|
description: 'Synchronisation des données du backoffice',
|
||||||
minimumSize:
|
isSyncing: _isSyncing,
|
||||||
MaterialStateProperty.all(Size(0, 30)),
|
syncCompleted: _syncCompleted,
|
||||||
overlayColor: MaterialStateProperty.all(
|
isError: _syncCompleted && !_backofficeSyncCompleted,
|
||||||
Colors.transparent),
|
),
|
||||||
shape: MaterialStateProperty.all(
|
SizedBox(height: 20),
|
||||||
RoundedRectangleBorder(
|
SyncItem(
|
||||||
borderRadius: BorderRadius.circular(5.0),
|
icon: Icons.photo,
|
||||||
)),
|
title: 'Photos',
|
||||||
side: MaterialStateProperty.all(
|
description: 'Synchronisation des photos',
|
||||||
BorderSide(color: SOFT_BLUE, width: 1.0),
|
isSyncing: _isSyncing,
|
||||||
)),
|
syncCompleted: _syncCompleted,
|
||||||
child: Text(
|
isError: _syncCompleted && !_photosSyncCompleted,
|
||||||
'Synchronize',
|
),
|
||||||
style: TextStyle(
|
SizedBox(height: 20),
|
||||||
color: SOFT_BLUE,
|
SyncItem(
|
||||||
fontWeight: FontWeight.bold,
|
icon: Icons.warning,
|
||||||
fontSize: 13),
|
title: 'Log',
|
||||||
textAlign: TextAlign.center,
|
description: 'Synchronisation des journaux d\'activité',
|
||||||
)),
|
isSyncing: _isSyncing,
|
||||||
|
syncCompleted: _syncCompleted,
|
||||||
|
isError: _syncCompleted && !_logSyncCompleted,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Future<void> navigateToPage(BuildContext context, int id_visite) async {
|
|
||||||
var connectivityResult = await (Connectivity().checkConnectivity());
|
class SyncItem extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
if (connectivityResult == ConnectivityResult.none) {
|
final String title;
|
||||||
Navigator.push(
|
final String description;
|
||||||
context,
|
final bool isSyncing;
|
||||||
MaterialPageRoute(
|
final bool syncCompleted;
|
||||||
builder: (_) => CheckConnectionPage(
|
final bool isError;
|
||||||
redirectPage: UploadPhotosPage(pp_id_visite: id_visite),
|
|
||||||
),
|
const SyncItem({
|
||||||
),
|
required this.icon,
|
||||||
);
|
required this.title,
|
||||||
} else {
|
required this.description,
|
||||||
Navigator.push(
|
required this.isSyncing,
|
||||||
context,
|
required this.syncCompleted,
|
||||||
MaterialPageRoute(
|
required this.isError,
|
||||||
builder: (_) => UploadPhotosPage(pp_id_visite: id_visite)));
|
});
|
||||||
}
|
|
||||||
}
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
/// Initializes data when the page loads.
|
return Column(
|
||||||
void loadData() {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
// initialization of data with all visits to be synchronized
|
children: [
|
||||||
tosyncVisitData = VisitModel.getToSyncVisits();
|
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<Color>(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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<SyncPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
AnimationController? _animationController;
|
||||||
|
late Animation<double> _rotationAnimation;
|
||||||
|
|
||||||
|
bool _isSyncing = false;
|
||||||
|
bool _syncCompleted = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
_rotationAnimation = Tween<double>(
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:mobdr/main.dart';
|
||||||
import 'package:mobdr/events.dart';
|
import 'package:mobdr/events.dart';
|
||||||
import 'package:mobdr/db/box_visit_photo.dart';
|
import 'package:mobdr/db/box_visit_photo.dart';
|
||||||
import 'package:mobdr/network/api_provider.dart';
|
import 'package:mobdr/network/api_provider.dart';
|
||||||
|
import 'package:mobdr/service/shared_prefs.dart';
|
||||||
|
|
||||||
class UploadPhotosPage extends StatefulWidget {
|
class UploadPhotosPage extends StatefulWidget {
|
||||||
final int pp_id_visite;
|
final int pp_id_visite;
|
||||||
|
|
@ -16,12 +17,12 @@ class UploadPhotosPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||||
bool _isUploading = false;
|
bool _isSyncing = false;
|
||||||
bool _isFinished = false;
|
bool _syncSuccessful = false;
|
||||||
int _totalUploaded = 0;
|
int _totalUploaded = 0;
|
||||||
int _totalPhotos = 0;
|
int _totalPhotos = 0;
|
||||||
|
|
||||||
late List<VisitPhoto> _photosList;
|
late List<VisitPhoto> _visitPhotosList;
|
||||||
|
|
||||||
ApiProvider _apiProvider = ApiProvider();
|
ApiProvider _apiProvider = ApiProvider();
|
||||||
|
|
||||||
|
|
@ -29,25 +30,17 @@ class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
// "visit" mode
|
loadData();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _uploadPhotos() async {
|
Future<void> _uploadVisitPhotos() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isUploading = true;
|
_isSyncing = true;
|
||||||
_isFinished = false;
|
_syncSuccessful = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// parse all photos
|
// parse all photos
|
||||||
for (var photo in _photosList) {
|
for (var photo in _visitPhotosList) {
|
||||||
int id_photo_mp4 = -1;
|
int id_photo_mp4 = -1;
|
||||||
bool isUpdatePhotoTypologie = true;
|
bool isUpdatePhotoTypologie = true;
|
||||||
bool isUpdatePhotoVisibility = true;
|
bool isUpdatePhotoVisibility = true;
|
||||||
|
|
@ -119,7 +112,8 @@ class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get unique id_visite values from _photosList
|
// Get unique id_visite values from _photosList
|
||||||
Set<int> uniqueIds = _photosList.map((photo) => photo.id_visite).toSet();
|
Set<int> uniqueIds =
|
||||||
|
_visitPhotosList.map((photo) => photo.id_visite).toSet();
|
||||||
|
|
||||||
// Send VisitPhotoCountEvent for each unique id_visite
|
// Send VisitPhotoCountEvent for each unique id_visite
|
||||||
for (int id_visite in uniqueIds) {
|
for (int id_visite in uniqueIds) {
|
||||||
|
|
@ -128,11 +122,45 @@ class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isUploading = false;
|
_isSyncing = false;
|
||||||
_isFinished = true;
|
_syncSuccessful = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _clearVisitPhotoCache() async {
|
||||||
|
List<VisitPhoto> _visitPhotoListTokeep;
|
||||||
|
Directory photosDir = Directory(SharedPrefs().photosDir);
|
||||||
|
|
||||||
|
_visitPhotoListTokeep = objectbox.getAllVisitPhotos();
|
||||||
|
|
||||||
|
// Get a list of all files in the "photos" directory
|
||||||
|
final List<FileSystemEntity> 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -160,7 +188,7 @@ class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: _isUploading,
|
visible: _isSyncing,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 150,
|
height: 150,
|
||||||
width: 150,
|
width: 150,
|
||||||
|
|
@ -175,7 +203,7 @@ class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: !_isUploading && _totalUploaded == _totalPhotos,
|
visible: !_isSyncing && _totalUploaded == _totalPhotos,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -195,7 +223,7 @@ class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: !_isUploading && _totalUploaded != _totalPhotos,
|
visible: !_isSyncing && _totalUploaded != _totalPhotos,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -216,17 +244,36 @@ class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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),
|
SizedBox(height: 16),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: !_isUploading,
|
visible: !_isSyncing,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
child:
|
child:
|
||||||
Text('Upload Photos ($_totalUploaded / $_totalPhotos)'),
|
Text('Upload Photos ($_totalUploaded / $_totalPhotos)'),
|
||||||
onPressed: _isFinished
|
onPressed: _syncSuccessful
|
||||||
? null
|
? null
|
||||||
: () {
|
: () async {
|
||||||
_uploadPhotos();
|
// 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<UploadPhotosPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,6 @@ class _VisitPhotoTypologyListPageState
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
ImportImageFromGallery();
|
ImportImageFromGallery();
|
||||||
/*Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => PhotoPickPage()));
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
child: ClipOval(
|
child: ClipOval(
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,7 @@ flutter:
|
||||||
- assets/images/logo_dark.png
|
- assets/images/logo_dark.png
|
||||||
- assets/images/logo_mp4.png
|
- assets/images/logo_mp4.png
|
||||||
- assets/images/logo_horizontal.png
|
- assets/images/logo_horizontal.png
|
||||||
|
- assets/images/synchro.png
|
||||||
- assets/images/onboarding/search_product.gif
|
- assets/images/onboarding/search_product.gif
|
||||||
- assets/images/process_timeline/status1.png
|
- assets/images/process_timeline/status1.png
|
||||||
- assets/images/process_timeline/status2.png
|
- assets/images/process_timeline/status2.png
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue