refactor: synchronization (step 2)
parent
06b33e9ec6
commit
d01f8e84b4
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
|
|
@ -1,23 +1,5 @@
|
|||
import 'package:event_bus_plus/event_bus_plus.dart';
|
||||
|
||||
class FollowAppEvent extends AppEvent {
|
||||
FollowAppEvent(this.username, {String? id});
|
||||
|
||||
final String username;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [username];
|
||||
}
|
||||
|
||||
class NewComment extends AppEvent {
|
||||
NewComment(this.text, {String? id});
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [text];
|
||||
}
|
||||
|
||||
class UrlEvent extends AppEvent {
|
||||
UrlEvent(this.url);
|
||||
|
||||
|
|
@ -45,3 +27,12 @@ class RefreshCalendarEvent extends AppEvent {
|
|||
@override
|
||||
List<Object?> get props => [refreshDate];
|
||||
}
|
||||
|
||||
class SynchronizationEvent extends AppEvent {
|
||||
SynchronizationEvent(this.isRunning);
|
||||
|
||||
final bool isRunning;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [isRunning];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ class _HomePageState extends State<HomePage>
|
|||
with SingleTickerProviderStateMixin {
|
||||
late PageController _pageController;
|
||||
late StreamSubscription subUrlEvent;
|
||||
late StreamSubscription subSynchronizationEvent;
|
||||
|
||||
bool _isSyncing = false;
|
||||
int _currentIndex = 0;
|
||||
|
||||
List<Widget> _contentPages = [];
|
||||
|
|
@ -27,7 +29,7 @@ class _HomePageState extends State<HomePage>
|
|||
@override
|
||||
void initState() {
|
||||
_contentPages = <Widget>[
|
||||
TabHomePage(),
|
||||
TabHomePage(navigateToTab: navigateToTab),
|
||||
SynchronizationPage(),
|
||||
TabMP4Page(),
|
||||
TabAccountPage(),
|
||||
|
|
@ -37,7 +39,7 @@ class _HomePageState extends State<HomePage>
|
|||
_pageController = PageController(initialPage: 0);
|
||||
_pageController.addListener(_handleTabSelection);
|
||||
|
||||
// Listen particular event
|
||||
// Listen Url event
|
||||
subUrlEvent = eventBus.on<UrlEvent>().listen((e) {
|
||||
setState(() {
|
||||
_currentIndex = 2;
|
||||
|
|
@ -46,6 +48,11 @@ class _HomePageState extends State<HomePage>
|
|||
});
|
||||
});
|
||||
|
||||
// Listen Synchronization Event
|
||||
subSynchronizationEvent = eventBus.on<SynchronizationEvent>().listen((e) {
|
||||
_isSyncing = e.isRunning;
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
|
@ -56,9 +63,19 @@ class _HomePageState extends State<HomePage>
|
|||
*/
|
||||
}
|
||||
|
||||
void navigateToTab(int tabIndex) {
|
||||
setState(() {
|
||||
_currentIndex = tabIndex;
|
||||
_pageController.jumpToPage(tabIndex);
|
||||
FocusScope.of(context).unfocus();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
subUrlEvent.cancel();
|
||||
subSynchronizationEvent.cancel();
|
||||
|
||||
_pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -77,11 +94,13 @@ class _HomePageState extends State<HomePage>
|
|||
type: BottomNavigationBarType.fixed,
|
||||
currentIndex: _currentIndex,
|
||||
onTap: (value) {
|
||||
setState(() {
|
||||
_currentIndex = value;
|
||||
_pageController.jumpToPage(value);
|
||||
FocusScope.of(context).unfocus();
|
||||
});
|
||||
if (!_isSyncing) {
|
||||
setState(() {
|
||||
_currentIndex = value;
|
||||
_pageController.jumpToPage(value);
|
||||
FocusScope.of(context).unfocus();
|
||||
});
|
||||
}
|
||||
},
|
||||
selectedFontSize: 8,
|
||||
unselectedFontSize: 8,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ 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/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';
|
||||
|
|
@ -27,6 +26,9 @@ import 'package:mobdr/ui/reusable/reusable_widget.dart';
|
|||
import 'package:mobdr/ui/reusable/cache_image_network.dart';
|
||||
|
||||
class TabHomePage extends StatefulWidget {
|
||||
final Function(int) navigateToTab;
|
||||
TabHomePage({required this.navigateToTab});
|
||||
|
||||
@override
|
||||
_TabHomePageState createState() => _TabHomePageState();
|
||||
}
|
||||
|
|
@ -238,19 +240,8 @@ class _TabHomePageState extends State<TabHomePage>
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final result = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SynchronizationPage()),
|
||||
);
|
||||
|
||||
// Refresh the widget if the synchronization was successful.
|
||||
if (result == true) {
|
||||
setState(() {
|
||||
loadData();
|
||||
});
|
||||
}
|
||||
onPressed: () {
|
||||
widget.navigateToTab(1); // switch to synchronization page
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
|
||||
class CheckConnectionPage extends StatefulWidget {
|
||||
final Widget redirectPage;
|
||||
|
||||
const CheckConnectionPage({Key? key, required this.redirectPage})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_CheckConnectionPageState createState() => _CheckConnectionPageState();
|
||||
}
|
||||
|
||||
class _CheckConnectionPageState extends State<CheckConnectionPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Mayday ..."),
|
||||
backgroundColor:
|
||||
Colors.red, // Définir la couleur de fond de l'appbar en rouge
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/no_connection.png',
|
||||
width: 250,
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"No internet connection",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Center(
|
||||
child: Text(
|
||||
"You are not connected. Check your connection.",
|
||||
style: TextStyle(fontSize: 14),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
child: const Text("Check Connection"),
|
||||
onPressed: () async {
|
||||
final connectivityResult =
|
||||
await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
await showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (_) => NetworkErrorDialog(
|
||||
onRetryPressed: () async {
|
||||
final connectivityResult =
|
||||
await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Please turn on your wifi or mobile data')));
|
||||
} else {
|
||||
Navigator.pop(context);
|
||||
// redirect to UploadPhotosPage here
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// redirect to UploadPhotosPage here
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NetworkErrorDialog extends StatelessWidget {
|
||||
const NetworkErrorDialog({Key? key, this.onRetryPressed}) : super(key: key);
|
||||
|
||||
final VoidCallback? onRetryPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('No Internet Connection'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.red, size: 48),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Please check your internet connection and try again.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Go Back'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onRetryPressed,
|
||||
child: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mobdr/network/api_provider.dart';
|
||||
|
||||
class SyncCalendarPage2 extends StatefulWidget {
|
||||
@override
|
||||
_SyncCalendarPage2State createState() => _SyncCalendarPage2State();
|
||||
}
|
||||
|
||||
class _SyncCalendarPage2State extends State<SyncCalendarPage2> {
|
||||
bool _isSyncing = false;
|
||||
bool _syncSuccessful = false;
|
||||
String? _syncErrorMessage;
|
||||
|
||||
Future<void> _syncData() async {
|
||||
setState(() {
|
||||
_isSyncing = true;
|
||||
});
|
||||
|
||||
final ApiProvider _apiProvider = ApiProvider();
|
||||
|
||||
final syncResult = await _apiProvider.SyncCalendar();
|
||||
|
||||
if (syncResult == 'OK') {
|
||||
setState(() {
|
||||
_isSyncing = false;
|
||||
_syncSuccessful = true;
|
||||
});
|
||||
|
||||
// Wait for 2 seconds before navigating back to the previous screen.
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
|
||||
_popScreen();
|
||||
} else {
|
||||
setState(() {
|
||||
_isSyncing = false;
|
||||
_syncSuccessful = false;
|
||||
_syncErrorMessage = syncResult;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _popScreen() {
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop(_syncSuccessful);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
// Disable the back button while the synchronization is in progress.
|
||||
onWillPop: () async => !_isSyncing,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Synchronisation'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (!_isSyncing && !_syncSuccessful && _syncErrorMessage == null)
|
||||
Text(
|
||||
'Appuyez sur le bouton pour lancer la synchronisation.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18.0),
|
||||
),
|
||||
if (_isSyncing)
|
||||
Text(
|
||||
'Synchronisation en cours...',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18.0),
|
||||
),
|
||||
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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_syncErrorMessage != null)
|
||||
Column(
|
||||
children: [
|
||||
Icon(Icons.error, size: 100.0, color: Colors.red),
|
||||
SizedBox(height: 16.0),
|
||||
Text(
|
||||
_syncErrorMessage!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18.0),
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
ElevatedButton(
|
||||
onPressed: _isSyncing ? null : _syncData,
|
||||
child: _isSyncing
|
||||
? CircularProgressIndicator()
|
||||
: Text('Relancer la synchronisation'),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!_isSyncing && !_syncSuccessful && _syncErrorMessage == null)
|
||||
ElevatedButton(
|
||||
onPressed: _isSyncing ? null : _syncData,
|
||||
child: _isSyncing
|
||||
? CircularProgressIndicator()
|
||||
: Text('Lancer la synchronisation'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,451 +0,0 @@
|
|||
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,6 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'dart:io';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
|
||||
import 'package:mobdr/main.dart';
|
||||
import 'package:mobdr/events.dart';
|
||||
import 'package:mobdr/service/shared_prefs.dart';
|
||||
import 'package:mobdr/network/api_provider.dart';
|
||||
import 'package:mobdr/db/box_visit_photo.dart';
|
||||
|
||||
class SynchronizationPage extends StatefulWidget {
|
||||
@override
|
||||
|
|
@ -13,6 +20,7 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
late Animation<double> _rotationAnimation;
|
||||
|
||||
bool _isSyncing = false;
|
||||
bool _isInternetConnexion = true;
|
||||
bool _syncCompleted = false;
|
||||
bool _backofficeSyncCompleted = false;
|
||||
bool _photosSyncCompleted = false;
|
||||
|
|
@ -21,10 +29,12 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(seconds: 2),
|
||||
);
|
||||
|
||||
_rotationAnimation =
|
||||
Tween<double>(begin: 0, end: 1).animate(_animationController);
|
||||
}
|
||||
|
|
@ -35,7 +45,139 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
void startSync() {
|
||||
void doSync() async {
|
||||
var connectivityResult = await (Connectivity().checkConnectivity());
|
||||
|
||||
// if no internet connection
|
||||
if (connectivityResult == ConnectivityResult.none) {
|
||||
setState(() {
|
||||
_isInternetConnexion = false;
|
||||
});
|
||||
} else {
|
||||
_isInternetConnexion = true;
|
||||
startSync();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _uploadVisitPhotos(ApiProvider _apiProvider) async {
|
||||
List<VisitPhoto> _visitPhotosList = objectbox.getAllVisitPhotos();
|
||||
int _totalUploaded = 0;
|
||||
|
||||
// parse all photos
|
||||
for (var photo in _visitPhotosList) {
|
||||
int id_photo_mp4 = -1;
|
||||
bool isUpdatePhotoTypologie = true;
|
||||
bool isUpdatePhotoVisibility = true;
|
||||
bool isUpdatePhotoCompetitor = true;
|
||||
bool isUpdatePhotoTags = true;
|
||||
|
||||
// if photo not already uploaded
|
||||
if (photo.id_photo_mp4 <= 0) {
|
||||
// try upload the photo
|
||||
id_photo_mp4 = await _apiProvider.uploadPhotoServlet(
|
||||
photo.id_visite, photo.getImage());
|
||||
} else
|
||||
id_photo_mp4 = photo.id_photo_mp4;
|
||||
|
||||
// the photo is saved in MP4
|
||||
if (id_photo_mp4 > 0) {
|
||||
// update photo typology
|
||||
isUpdatePhotoTypologie = await _apiProvider.updatePhotoTypology(
|
||||
photo.id_visite, id_photo_mp4, photo.id_photo_typologie);
|
||||
|
||||
// update photo visibility
|
||||
if (photo.photo_principale == 1 || photo.photo_privee == 1) {
|
||||
isUpdatePhotoVisibility = await _apiProvider.updatePhotoVisibility(
|
||||
photo.id_visite,
|
||||
id_photo_mp4,
|
||||
photo.photo_principale,
|
||||
photo.photo_privee);
|
||||
}
|
||||
|
||||
// update photo tags
|
||||
if (photo.tags.isNotEmpty) {
|
||||
isUpdatePhotoTags = await _apiProvider.updatePhotoTags(
|
||||
photo.id_visite, id_photo_mp4, photo.tags);
|
||||
}
|
||||
|
||||
// update photo competitor
|
||||
if (photo.id_concurrence_lien > 0) {
|
||||
isUpdatePhotoCompetitor = await _apiProvider.updatePhotoConpetitor(
|
||||
photo.id_visite, id_photo_mp4, photo.id_concurrence_lien);
|
||||
}
|
||||
}
|
||||
|
||||
if (id_photo_mp4 > 0 &&
|
||||
isUpdatePhotoTypologie == true &&
|
||||
isUpdatePhotoVisibility == true &&
|
||||
isUpdatePhotoTags == true &&
|
||||
isUpdatePhotoCompetitor == true) {
|
||||
// delete photo in database
|
||||
objectbox.delPhotoById(photo.id);
|
||||
|
||||
// delete the file
|
||||
File file = File(photo.getImage());
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
|
||||
_totalUploaded++;
|
||||
} else {
|
||||
// if successful upload
|
||||
if (id_photo_mp4 != -1) {
|
||||
photo.id_photo_mp4 = id_photo_mp4;
|
||||
|
||||
// save MP4 id in database
|
||||
objectbox.putPhotoIdMP4(photo.id, id_photo_mp4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_photosSyncCompleted = (_totalUploaded == _visitPhotosList.length);
|
||||
|
||||
// Get unique id_visite values from _photosList
|
||||
Set<int> uniqueIds =
|
||||
_visitPhotosList.map((photo) => photo.id_visite).toSet();
|
||||
|
||||
// Send VisitPhotoCountEvent for each unique id_visite
|
||||
for (int id_visite in uniqueIds) {
|
||||
eventBus.fire(VisitPhotoCountEvent(
|
||||
id_visite, objectbox.getVisitPhotoCount(id_visite)));
|
||||
}
|
||||
}
|
||||
|
||||
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 startSync() async {
|
||||
// disable navigation bar buttons
|
||||
eventBus.fire(SynchronizationEvent(true));
|
||||
|
||||
setState(() {
|
||||
_isSyncing = true;
|
||||
_syncCompleted = false;
|
||||
|
|
@ -46,19 +188,44 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
|
||||
_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();
|
||||
final ApiProvider _apiProvider = ApiProvider();
|
||||
|
||||
// synchronous calendar synchronization
|
||||
final syncCalendarResult = await _apiProvider.SyncCalendar();
|
||||
|
||||
// backoffice synchronization OK ?
|
||||
if (syncCalendarResult == 'OK') {
|
||||
_backofficeSyncCompleted = true;
|
||||
}
|
||||
|
||||
// upload photo to server
|
||||
await _uploadVisitPhotos(_apiProvider);
|
||||
|
||||
// TODO:
|
||||
// supprimer les visites "ancienne" sans photo !!
|
||||
|
||||
// deletes photos that are no longer in any visits
|
||||
await _clearVisitPhotoCache();
|
||||
|
||||
// last synchronization date
|
||||
SharedPrefs().lastCalendarRefresh =
|
||||
DateFormat('dd/MM/yyyy HH:mm').format(DateTime.now());
|
||||
|
||||
// TODO: je pense que comme il y a cet event (qui reload tout) on est plus obligé
|
||||
// de lancer l'autre évent pour les count() de photo.
|
||||
//
|
||||
// de plus il faut supprimer les visites qui n'ont plus de photo !!!
|
||||
eventBus.fire(RefreshCalendarEvent(SharedPrefs().lastCalendarRefresh));
|
||||
|
||||
setState(() {
|
||||
_isSyncing = false;
|
||||
_syncCompleted = true;
|
||||
});
|
||||
|
||||
_animationController.stop();
|
||||
|
||||
// enable navigation bar buttons
|
||||
eventBus.fire(SynchronizationEvent(false));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -79,7 +246,10 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.blue,
|
||||
color: _isInternetConnexion
|
||||
? Colors.blue
|
||||
: Colors
|
||||
.red, // Modifier la couleur en fonction de la connexion Internet
|
||||
),
|
||||
child: IconButton(
|
||||
icon: AnimatedBuilder(
|
||||
|
|
@ -88,23 +258,35 @@ class _SynchronizationPageState extends State<SynchronizationPage>
|
|||
return Transform.rotate(
|
||||
angle: _rotationAnimation.value * 2.0 * 3.14,
|
||||
child: Icon(
|
||||
Icons.refresh,
|
||||
_isInternetConnexion
|
||||
? Icons.refresh
|
||||
: Icons
|
||||
.signal_wifi_off, // Modifier l'icône en fonction de la connexion Internet
|
||||
color: Colors.white,
|
||||
size: 80,
|
||||
size: 70,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onPressed: _isSyncing ? null : startSync,
|
||||
onPressed: _isSyncing ? null : doSync,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
SharedPrefs().lastCalendarRefresh.isNotEmpty
|
||||
? SharedPrefs().lastCalendarRefresh
|
||||
: "Never",
|
||||
style: TextStyle(fontSize: 16),
|
||||
!_isInternetConnexion
|
||||
? "No internet connection ..."
|
||||
: (SharedPrefs().lastCalendarRefresh.isNotEmpty
|
||||
? SharedPrefs().lastCalendarRefresh
|
||||
: "Never"),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: !_isInternetConnexion
|
||||
? Colors.red
|
||||
: Colors
|
||||
.black, // Modifier la couleur du texte en fonction de votre préférence
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
|
|
|
|||
|
|
@ -1,228 +0,0 @@
|
|||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,299 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
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;
|
||||
|
||||
UploadPhotosPage({required this.pp_id_visite});
|
||||
|
||||
@override
|
||||
_UploadPhotosPageState createState() => _UploadPhotosPageState();
|
||||
}
|
||||
|
||||
class _UploadPhotosPageState extends State<UploadPhotosPage> {
|
||||
bool _isSyncing = false;
|
||||
bool _syncSuccessful = false;
|
||||
int _totalUploaded = 0;
|
||||
int _totalPhotos = 0;
|
||||
|
||||
late List<VisitPhoto> _visitPhotosList;
|
||||
|
||||
ApiProvider _apiProvider = ApiProvider();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
loadData();
|
||||
}
|
||||
|
||||
Future<void> _uploadVisitPhotos() async {
|
||||
setState(() {
|
||||
_isSyncing = true;
|
||||
_syncSuccessful = false;
|
||||
});
|
||||
|
||||
// parse all photos
|
||||
for (var photo in _visitPhotosList) {
|
||||
int id_photo_mp4 = -1;
|
||||
bool isUpdatePhotoTypologie = true;
|
||||
bool isUpdatePhotoVisibility = true;
|
||||
bool isUpdatePhotoCompetitor = true;
|
||||
bool isUpdatePhotoTags = true;
|
||||
|
||||
// if photo not already uploaded
|
||||
if (photo.id_photo_mp4 <= 0) {
|
||||
// try upload the photo
|
||||
id_photo_mp4 = await _apiProvider.uploadPhotoServlet(
|
||||
photo.id_visite, photo.getImage());
|
||||
} else
|
||||
id_photo_mp4 = photo.id_photo_mp4;
|
||||
|
||||
// the photo is saved in MP4
|
||||
if (id_photo_mp4 > 0) {
|
||||
// update photo typology
|
||||
isUpdatePhotoTypologie = await _apiProvider.updatePhotoTypology(
|
||||
photo.id_visite, id_photo_mp4, photo.id_photo_typologie);
|
||||
|
||||
// update photo visibility
|
||||
if (photo.photo_principale == 1 || photo.photo_privee == 1) {
|
||||
isUpdatePhotoVisibility = await _apiProvider.updatePhotoVisibility(
|
||||
photo.id_visite,
|
||||
id_photo_mp4,
|
||||
photo.photo_principale,
|
||||
photo.photo_privee);
|
||||
}
|
||||
|
||||
// update photo tags
|
||||
if (photo.tags.isNotEmpty) {
|
||||
isUpdatePhotoTags = await _apiProvider.updatePhotoTags(
|
||||
photo.id_visite, id_photo_mp4, photo.tags);
|
||||
}
|
||||
|
||||
// update photo competitor
|
||||
if (photo.id_concurrence_lien > 0) {
|
||||
isUpdatePhotoCompetitor = await _apiProvider.updatePhotoConpetitor(
|
||||
photo.id_visite, id_photo_mp4, photo.id_concurrence_lien);
|
||||
}
|
||||
}
|
||||
|
||||
if (id_photo_mp4 > 0 &&
|
||||
isUpdatePhotoTypologie == true &&
|
||||
isUpdatePhotoVisibility == true &&
|
||||
isUpdatePhotoTags == true &&
|
||||
isUpdatePhotoCompetitor == true) {
|
||||
// delete photo in database
|
||||
objectbox.delPhotoById(photo.id);
|
||||
|
||||
// delete the file
|
||||
File file = File(photo.getImage());
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
|
||||
_totalUploaded++;
|
||||
} else {
|
||||
// if successful upload
|
||||
if (id_photo_mp4 != -1) {
|
||||
photo.id_photo_mp4 = id_photo_mp4;
|
||||
|
||||
// save MP4 id in database
|
||||
objectbox.putPhotoIdMP4(photo.id, id_photo_mp4);
|
||||
}
|
||||
}
|
||||
|
||||
//await Future.delayed(Duration(seconds: 5));
|
||||
}
|
||||
|
||||
// Get unique id_visite values from _photosList
|
||||
Set<int> uniqueIds =
|
||||
_visitPhotosList.map((photo) => photo.id_visite).toSet();
|
||||
|
||||
// Send VisitPhotoCountEvent for each unique id_visite
|
||||
for (int id_visite in uniqueIds) {
|
||||
eventBus.fire(VisitPhotoCountEvent(
|
||||
id_visite, objectbox.getVisitPhotoCount(id_visite)));
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isSyncing = false;
|
||||
_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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text('Upload Photos'),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'Upload Photos',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: Visibility(
|
||||
visible: _isSyncing,
|
||||
child: Container(
|
||||
height: 150,
|
||||
width: 150,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).primaryColor,
|
||||
),
|
||||
strokeWidth: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Visibility(
|
||||
visible: !_isSyncing && _totalUploaded == _totalPhotos,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
size: 48,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Upload complete',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Visibility(
|
||||
visible: !_isSyncing && _totalUploaded != _totalPhotos,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cloud_upload,
|
||||
size: 48,
|
||||
color: Colors.grey,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Tap the button below to upload your photos',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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: !_isSyncing,
|
||||
child: ElevatedButton(
|
||||
child:
|
||||
Text('Upload Photos ($_totalUploaded / $_totalPhotos)'),
|
||||
onPressed: _syncSuccessful
|
||||
? null
|
||||
: () async {
|
||||
// upload photo to server
|
||||
await _uploadVisitPhotos();
|
||||
|
||||
// deletes photos that are no longer in any visits
|
||||
await _clearVisitPhotoCache();
|
||||
|
||||
// go back
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
//,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -170,8 +170,7 @@ flutter:
|
|||
- assets/images/process_timeline/status4.png
|
||||
- assets/images/process_timeline/status5.png
|
||||
- assets/images/simulator.jpeg
|
||||
- assets/images/no_connection.png
|
||||
|
||||
|
||||
- assets/lang/fr.json
|
||||
- assets/lang/en.json
|
||||
- assets/lang/id.json
|
||||
|
|
|
|||
Loading…
Reference in New Issue