diff --git a/lib/components/main_appbar_component.dart b/lib/components/main_appbar_component.dart index 18c7722..917b371 100644 --- a/lib/components/main_appbar_component.dart +++ b/lib/components/main_appbar_component.dart @@ -1,5 +1,7 @@ +import 'package:e_scan/services/connectivity_service.dart'; import 'package:e_scan/themes/app_theme.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; class MainAppbarComponent extends StatelessWidget implements PreferredSizeWidget { @@ -52,7 +54,26 @@ class MainAppbarComponent extends StatelessWidget ), ], ), - actions: actions, + actions: [ + ...actions ?? [], + Consumer( + builder: (context, ref, _) { + final isConnected = ref.watch(isConnectedProvider); + if (isConnected) { + return SizedBox.shrink(); + } else { + return Row( + spacing: 5, + children: [ + Icon(Icons.cloud_off, color: Colors.white), + Text('Hors ligne', style: TextStyle(color: Colors.white)), + SizedBox(width: 10), + ], + ); + } + }, + ), + ], toolbarHeight: 60, backgroundColor: AppTheme.of(context).primary, elevation: 4, diff --git a/lib/pages/operation/reception/reception_page.dart b/lib/pages/operation/reception/reception_page.dart index 103b747..e596c1b 100644 --- a/lib/pages/operation/reception/reception_page.dart +++ b/lib/pages/operation/reception/reception_page.dart @@ -35,76 +35,88 @@ class _ReceptionPageState extends ConsumerState { title: "Réceptions", subTitle: "Opérations d'Entrepôt", ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ListView( - children: [ - const SizedBox(height: 16), - QuickActionComponent( - onTapAdd: () {}, - onTapScan: () {}, - onTapSearch: () {}, - ), - const SizedBox(height: 16), - Consumer( - builder: (_, WidgetRef ref, _) { - final state = ref.watch(receptionPageModelProvider); - if (state.loadingReceptions) { - return Center(child: LoadingProgressComponent()); - } - return Column( - spacing: 10, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Text( - // 'Réceptions', - // style: AppTheme.of( - // context, - // ).bodyMedium.copyWith(fontWeight: FontWeight.bold), - // ), - state.receptions.isEmpty == true - ? Text('No data') - : ListView.builder( - physics: NeverScrollableScrollPhysics(), - primary: true, - shrinkWrap: true, - itemCount: state.receptions.length, - itemBuilder: (context, index) { - final reception = state.receptions[index]; - return GestureDetector( - onTap: () { - final id = reception.id; - ReceptionDetailsRoute( - receptionId: id, - ).push(context); - }, - child: StockPickingCard( - synchronized: reception.synchronized == true, - margin: EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, + body: RefreshIndicator( + color: AppTheme.of(context).white, + backgroundColor: AppTheme.of(context).primary, + onRefresh: () async { + await ref + .read(receptionPageModelProvider.notifier) + .getAllReceptions(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ListView( + children: [ + const SizedBox(height: 16), + QuickActionComponent( + onTapAdd: () {}, + onTapScan: () {}, + onTapSearch: () {}, + ), + const SizedBox(height: 16), + Consumer( + builder: (_, WidgetRef ref, _) { + final state = ref.watch(receptionPageModelProvider); + if (state.loadingReceptions) { + return Center(child: LoadingProgressComponent()); + } + return Column( + spacing: 10, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Text( + // 'Réceptions', + // style: AppTheme.of( + // context, + // ).bodyMedium.copyWith(fontWeight: FontWeight.bold), + // ), + state.receptions.isEmpty == true + ? Text('No data') + : ListView.builder( + physics: NeverScrollableScrollPhysics(), + primary: true, + shrinkWrap: true, + itemCount: state.receptions.length, + itemBuilder: (context, index) { + final reception = state.receptions[index]; + return GestureDetector( + onTap: () { + final id = reception.id; + ReceptionDetailsRoute( + receptionId: id, + ).push(context); + }, + child: StockPickingCard( + synchronized: + reception.synchronized == true, + margin: EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + isDone: reception.isDone == true, + reference: reception.name ?? '', + from: reception + .locationId + .target + ?.completeName, + to: reception + .locationDestId + .target + ?.completeName, + contact: + reception.partnerId.target?.displayName, + origin: reception.origin, + status: reception.state, ), - isDone: reception.isDone == true, - reference: reception.name ?? '', - from: - reception.locationId.target?.completeName, - to: reception - .locationDestId - .target - ?.completeName, - contact: - reception.partnerId.target?.displayName, - origin: reception.origin, - status: reception.state, - ), - ); - }, - ), - ], - ); - }, - ), - ], + ); + }, + ), + ], + ); + }, + ), + ], + ), ), ), ); diff --git a/lib/pages/operation/reception/reception_page_model.dart b/lib/pages/operation/reception/reception_page_model.dart index cbba071..4a5f1f9 100644 --- a/lib/pages/operation/reception/reception_page_model.dart +++ b/lib/pages/operation/reception/reception_page_model.dart @@ -1,5 +1,7 @@ import 'package:e_scan/backend/api/api_calls.dart'; import 'package:e_scan/backend/objectbox/entities/stock_picking/stock_picking_record_entity.dart'; +import 'package:e_scan/backend/objectbox/objectbox_manager.dart'; +import 'package:e_scan/backend/schema/stock_picking/update_move_line_dto.dart'; import 'package:e_scan/backend/schema/user/user_struct.dart'; import 'package:e_scan/services/secure_storage.dart'; import 'package:e_scan/services/token_provider.dart'; @@ -38,7 +40,57 @@ class ReceptionPageModel extends StateNotifier { state = state.copyWith(user: user, loadingUser: false); } + Future save({ + required int receptionId, + VoidCallback? onSuccess, + VoidCallback? onError, + }) async { + try { + final stockPickingRecords = objectboxManager.store + .box(); + final stockPikingEntity = stockPickingRecords.get(receptionId); + final moveLinesDto = + stockPikingEntity?.moveLineIdsWithoutPackage + .map( + (m) => UpdateMoveLineDto( + operation: 1, + moveLineId: m.id, + values: {"quantity": m.quantity}, + ), + ) + .toList() ?? + []; + final res = await ApiCalls.updateAllMoveLineOnStockPicking( + stockPickingId: receptionId, + moveLineDto: moveLinesDto, + ); + if (res) { + stockPikingEntity?.synchronized = true; + stockPickingRecords.put(stockPikingEntity!); + onSuccess?.call(); + } else { + onError?.call(); + } + } catch (e) { + onError?.call(); + } + } + + Future synchroAllData() async { + final stockPickingRecords = objectboxManager.store + .box(); + final records = stockPickingRecords + .query(StockPickingRecordEntity_.synchronized.equals(false)) + .build() + .find(); + for (var rec in records) { + await save(receptionId: rec.id); + } + } + Future getAllReceptions() async { + // sync all data first + await synchroAllData(); try { state = state.copyWith(loadingReceptions: true); final res = await ApiCalls.getAllStockPiking(); diff --git a/lib/services/connectivity_service.dart b/lib/services/connectivity_service.dart new file mode 100644 index 0000000..3946fad --- /dev/null +++ b/lib/services/connectivity_service.dart @@ -0,0 +1,37 @@ +import 'dart:async'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/// Provider qui retourne un booléen: connecté ou non +final isConnectedProvider = + StateNotifierProvider( + (ref) => ConnectivityBoolNotifier(), + ); + +class ConnectivityBoolNotifier extends StateNotifier { + ConnectivityBoolNotifier() : super(false) { + _init(); + _subscription = Connectivity().onConnectivityChanged.listen( + (results) => state = _isConnected(results), + ); + } + late final StreamSubscription> _subscription; + + Future _init() async { + final result = await Connectivity().checkConnectivity(); + state = _isConnected(result); + } + + bool _isConnected(List result) { + return (result).contains(ConnectivityResult.mobile) || + (result).contains(ConnectivityResult.wifi) || + (result).contains(ConnectivityResult.vpn) || + (result).contains(ConnectivityResult.ethernet); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } +}