diff --git a/modules/legacy/modules/control_panel/lib/src/features/control_panel/presentation/control_panel_screen.dart b/modules/legacy/modules/control_panel/lib/src/features/control_panel/presentation/control_panel_screen.dart index babb76d4..a691fcb4 100644 --- a/modules/legacy/modules/control_panel/lib/src/features/control_panel/presentation/control_panel_screen.dart +++ b/modules/legacy/modules/control_panel/lib/src/features/control_panel/presentation/control_panel_screen.dart @@ -41,14 +41,12 @@ class ControlPanelScreen extends ConsumerWidget { body: asyncState.when( skipLoadingOnReload: true, loading: () => const Center(child: CircularProgressIndicator()), - error: (error, _) => Center( - child: Padding( - padding: const EdgeInsets.all(24), - child: Text( - formatErrorMessage(error), - textAlign: TextAlign.center, - ), - ), + error: (error, _) => RefreshableErrorState( + message: formatErrorMessage(error), + onRefresh: () async { + ref.invalidate(controlPanelViewModelProvider); + await ref.read(controlPanelViewModelProvider.future); + }, ), data: (state) => SafeArea( child: Container( @@ -62,22 +60,30 @@ class ControlPanelScreen extends ConsumerWidget { ), SizedBox(height: SizeUtils.getByScreen(small: 12, big: 14)), Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _MenuSection(navigationContract: navigationContract), - SizedBox( - height: SizeUtils.getByScreen(small: 16, big: 22), - ), - _MapSection( - state: state, - navigationContract: navigationContract, - ), - SizedBox( - height: SizeUtils.getByScreen(small: 14, big: 13), - ), - ], + child: RefreshIndicator( + color: theme.getColorFor(ThemeCode.legacyPrimary), + onRefresh: () async { + ref.invalidate(legacyDevicesProvider); + await ref.read(controlPanelViewModelProvider.future); + }, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _MenuSection(navigationContract: navigationContract), + SizedBox( + height: SizeUtils.getByScreen(small: 16, big: 22), + ), + _MapSection( + state: state, + navigationContract: navigationContract, + ), + SizedBox( + height: SizeUtils.getByScreen(small: 14, big: 13), + ), + ], + ), ), ), ), diff --git a/modules/legacy/modules/location/lib/src/features/location/presentation/location_screen.dart b/modules/legacy/modules/location/lib/src/features/location/presentation/location_screen.dart index e77f2daa..7290445f 100644 --- a/modules/legacy/modules/location/lib/src/features/location/presentation/location_screen.dart +++ b/modules/legacy/modules/location/lib/src/features/location/presentation/location_screen.dart @@ -92,26 +92,21 @@ class LocationScreen extends ConsumerWidget { body: asyncControlPanelState.when( skipLoadingOnReload: true, loading: () => const Center(child: CircularProgressIndicator()), - error: (error, _) => Center( - child: Padding( - padding: const EdgeInsets.all(24), - child: Text( - context.translate(I18n.errorGeneric), - textAlign: TextAlign.center, - ), - ), + error: (error, _) => RefreshableErrorState( + onRefresh: () async { + ref.invalidate(controlPanelViewModelProvider); + ref.invalidate(locationViewModelProvider); + await ref.read(controlPanelViewModelProvider.future); + }, ), data: (controlPanelState) => asyncLocationState.when( skipLoadingOnReload: true, loading: () => const Center(child: CircularProgressIndicator()), - error: (error, _) => Center( - child: Padding( - padding: const EdgeInsets.all(24), - child: Text( - context.translate(I18n.errorGeneric), - textAlign: TextAlign.center, - ), - ), + error: (error, _) => RefreshableErrorState( + onRefresh: () async { + ref.invalidate(locationViewModelProvider); + await ref.read(locationViewModelProvider.future); + }, ), data: (locationState) => LocationMap( selectedPosition: controlPanelState.selectedPosition, @@ -139,3 +134,4 @@ class LocationScreen extends ConsumerWidget { ); } } + diff --git a/modules/legacy/packages/legacy_shared/lib/legacy_shared.dart b/modules/legacy/packages/legacy_shared/lib/legacy_shared.dart index a0489544..f598b02f 100644 --- a/modules/legacy/packages/legacy_shared/lib/legacy_shared.dart +++ b/modules/legacy/packages/legacy_shared/lib/legacy_shared.dart @@ -2,6 +2,7 @@ export 'src/providers/map_style_provider.dart'; export 'src/widgets/layouts/page_layout.dart'; export 'src/components/section_button.dart'; export 'src/widgets/pulsing_location_marker.dart'; +export 'src/widgets/refreshable_error_state.dart'; export 'src/widgets/week_day_chips.dart'; export 'src/components/menu_button.dart'; export 'src/data/models/device_response_model.dart'; diff --git a/modules/legacy/packages/legacy_shared/lib/src/widgets/refreshable_error_state.dart b/modules/legacy/packages/legacy_shared/lib/src/widgets/refreshable_error_state.dart new file mode 100644 index 00000000..6743af65 --- /dev/null +++ b/modules/legacy/packages/legacy_shared/lib/src/widgets/refreshable_error_state.dart @@ -0,0 +1,73 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:sf_localizations/sf_localizations.dart'; + +class RefreshableErrorState extends ConsumerWidget { + const RefreshableErrorState({ + super.key, + required this.onRefresh, + this.message, + }); + + final Future Function() onRefresh; + final String? message; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themePortProvider); + final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary); + final textColor = theme.getColorFor(ThemeCode.textPrimary); + final errorMessage = message ?? context.translate(I18n.errorGeneric); + + return RefreshIndicator( + onRefresh: onRefresh, + color: primaryColor, + child: LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: Padding( + padding: const EdgeInsets.all(24), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.cloud_off_outlined, + size: 56, + color: textColor.withValues(alpha: 0.4), + ), + const SizedBox(height: 16), + Text( + errorMessage, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 15, color: textColor), + ), + const SizedBox(height: 24), + Icon( + Icons.arrow_downward_rounded, + size: 20, + color: primaryColor, + ), + const SizedBox(height: 6), + Text( + context.translate(I18n.pullDownToRetry), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 13, + color: primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/sf_localizations/assets/l10n/de.json b/packages/sf_localizations/assets/l10n/de.json index 273cc6de..82e9709a 100644 --- a/packages/sf_localizations/assets/l10n/de.json +++ b/packages/sf_localizations/assets/l10n/de.json @@ -668,6 +668,7 @@ "frequentPlaceUpdated": "Häufiger Ort aktualisiert", "frequentPlaceDeleted": "Häufiger Ort gelöscht", "errorGeneric": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.", + "pullDownToRetry": "Zum Wiederholen nach unten ziehen", "errorGeofenceCreate": "Die Sicherheitszone konnte nicht erstellt werden", "errorGeofenceUpdate": "Die Sicherheitszone konnte nicht aktualisiert werden", "errorGeofenceDelete": "Die Sicherheitszone konnte nicht gelöscht werden", diff --git a/packages/sf_localizations/assets/l10n/en.json b/packages/sf_localizations/assets/l10n/en.json index 2edaee69..463466e7 100755 --- a/packages/sf_localizations/assets/l10n/en.json +++ b/packages/sf_localizations/assets/l10n/en.json @@ -836,6 +836,7 @@ "frequentPlaceUpdated": "Frequent place updated", "frequentPlaceDeleted": "Frequent place deleted", "errorGeneric": "Something went wrong. Please try again.", + "pullDownToRetry": "Pull down to retry", "errorGeofenceCreate": "Could not create the safety zone", "errorGeofenceUpdate": "Could not update the safety zone", "errorGeofenceDelete": "Could not delete the safety zone", diff --git a/packages/sf_localizations/assets/l10n/es.json b/packages/sf_localizations/assets/l10n/es.json index 17c7faa6..88cb13d3 100644 --- a/packages/sf_localizations/assets/l10n/es.json +++ b/packages/sf_localizations/assets/l10n/es.json @@ -837,6 +837,7 @@ "frequentPlaceUpdated": "Lugar frecuente actualizado", "frequentPlaceDeleted": "Lugar frecuente eliminado", "errorGeneric": "Algo salió mal. Inténtalo de nuevo.", + "pullDownToRetry": "Desliza hacia abajo para reintentar", "errorGeofenceCreate": "No se pudo crear la zona de seguridad", "errorGeofenceUpdate": "No se pudo actualizar la zona de seguridad", "errorGeofenceDelete": "No se pudo eliminar la zona de seguridad", diff --git a/packages/sf_localizations/assets/l10n/fr.json b/packages/sf_localizations/assets/l10n/fr.json index 5d923365..ad4eb1b1 100644 --- a/packages/sf_localizations/assets/l10n/fr.json +++ b/packages/sf_localizations/assets/l10n/fr.json @@ -668,6 +668,7 @@ "frequentPlaceUpdated": "Lieu fréquent mis à jour", "frequentPlaceDeleted": "Lieu fréquent supprimé", "errorGeneric": "Une erreur est survenue. Veuillez réessayer.", + "pullDownToRetry": "Tirez vers le bas pour réessayer", "errorGeofenceCreate": "Impossible de créer la zone de sécurité", "errorGeofenceUpdate": "Impossible de mettre à jour la zone de sécurité", "errorGeofenceDelete": "Impossible de supprimer la zone de sécurité", diff --git a/packages/sf_localizations/assets/l10n/it.json b/packages/sf_localizations/assets/l10n/it.json index 3a25d000..c2d90125 100644 --- a/packages/sf_localizations/assets/l10n/it.json +++ b/packages/sf_localizations/assets/l10n/it.json @@ -668,6 +668,7 @@ "frequentPlaceUpdated": "Luogo frequente aggiornato", "frequentPlaceDeleted": "Luogo frequente eliminato", "errorGeneric": "Qualcosa è andato storto. Riprova.", + "pullDownToRetry": "Trascina verso il basso per riprovare", "errorGeofenceCreate": "Impossibile creare la zona di sicurezza", "errorGeofenceUpdate": "Impossibile aggiornare la zona di sicurezza", "errorGeofenceDelete": "Impossibile eliminare la zona di sicurezza", diff --git a/packages/sf_localizations/assets/l10n/pt.json b/packages/sf_localizations/assets/l10n/pt.json index 29e52ba9..1a5b4eb8 100644 --- a/packages/sf_localizations/assets/l10n/pt.json +++ b/packages/sf_localizations/assets/l10n/pt.json @@ -668,6 +668,7 @@ "frequentPlaceUpdated": "Local frequente atualizado", "frequentPlaceDeleted": "Local frequente eliminado", "errorGeneric": "Algo correu mal. Tente novamente.", + "pullDownToRetry": "Deslize para baixo para tentar novamente", "errorGeofenceCreate": "Não foi possível criar a zona de segurança", "errorGeofenceUpdate": "Não foi possível atualizar a zona de segurança", "errorGeofenceDelete": "Não foi possível eliminar a zona de segurança", diff --git a/packages/sf_localizations/lib/src/generated/i18n.dart b/packages/sf_localizations/lib/src/generated/i18n.dart index bc39f250..1f552bee 100755 --- a/packages/sf_localizations/lib/src/generated/i18n.dart +++ b/packages/sf_localizations/lib/src/generated/i18n.dart @@ -413,6 +413,7 @@ class I18n { static const String errorFrequentPlaceDelete = 'errorFrequentPlaceDelete'; static const String errorFrequentPlaceUpdate = 'errorFrequentPlaceUpdate'; static const String errorGeneric = 'errorGeneric'; + static const String pullDownToRetry = 'pullDownToRetry'; static const String errorGeofenceCreate = 'errorGeofenceCreate'; static const String errorGeofenceDelete = 'errorGeofenceDelete'; static const String errorGeofenceUpdate = 'errorGeofenceUpdate';