feat(legacy): block device commands when watch is disconnected

This commit is contained in:
2026-04-16 23:48:07 +02:00
parent 769e8fea27
commit ddc5086b3b
36 changed files with 169 additions and 25 deletions

View File

@@ -38,6 +38,7 @@ class PedometerToggle extends ConsumerWidget {
value: enabled,
activeTrackColor: theme.getColorFor(ThemeCode.legacyPrimary),
onChanged: (value) async {
if (!guardDeviceCommand(context, ref)) return;
final success = await ref
.read(activityMeterViewModelProvider.notifier)
.togglePedometer(enabled: value);

View File

@@ -1,6 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -94,7 +95,12 @@ class BackgroundImageScreen extends ConsumerWidget {
shape: BoxShape.circle,
),
child: IconButton(
onPressed: state.isSaving ? null : vm.uploadPhoto,
onPressed: state.isSaving
? null
: () {
if (!guardDeviceCommand(context, ref)) return;
vm.uploadPhoto();
},
icon: Icon(
Icons.add_photo_alternate_outlined,
color: Colors.white,
@@ -112,10 +118,19 @@ class BackgroundImageScreen extends ConsumerWidget {
: state.isSaving
? const Center(child: CircularProgressIndicator())
: state.photos.isEmpty
? _EmptyState(onUpload: vm.uploadPhoto, primaryColor: primaryColor)
? _EmptyState(
onUpload: () {
if (!guardDeviceCommand(context, ref)) return;
vm.uploadPhoto();
},
primaryColor: primaryColor,
)
: _PhotoGrid(
state: state,
onPhotoTap: vm.setAsBackground,
onPhotoTap: (id) {
if (!guardDeviceCommand(context, ref)) return;
vm.setAsBackground(id);
},
primaryColor: primaryColor,
),
),

View File

@@ -59,6 +59,7 @@ class ContactsScreen extends ConsumerWidget {
child: InkWell(
customBorder: const CircleBorder(),
onTap: () {
if (!guardDeviceCommand(context, ref)) return;
if (state.contacts.length >= state.maxContacts) {
showTopSnackbar(
context,

View File

@@ -155,7 +155,10 @@ class DoNotDisturbScreen extends ConsumerWidget {
child: isSaving
? const Center(child: CircularProgressIndicator())
: PrimaryButton(
onPressed: vm.save,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
vm.save();
},
text: context.translate(I18n.doNotDisturbSave),
color: primaryColor,
),

View File

@@ -109,6 +109,7 @@ class _HealthScreenState extends ConsumerState<HealthScreen>
options: device.capabilities!.heartbeats!.options,
theme: theme,
onChanged: (frequency) async {
if (!guardDeviceCommand(context, ref)) return;
final success = await vm.updateHeartRateFrequency(
frequency: frequency,
);
@@ -195,7 +196,12 @@ class _SaveSection extends ConsumerWidget {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: PrimaryButton(
onPressed: isMeasuring ? null : () => vm.measure(),
onPressed: isMeasuring
? null
: () {
if (!guardDeviceCommand(context, ref)) return;
vm.measure();
},
text: isMeasuring ? '...' : context.translate(I18n.measure),
color: theme.getColorFor(ThemeCode.legacyPrimary),
),

View File

@@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
import 'package:legacy_shared/legacy_shared.dart';
import '../state/locate_device_view_model.dart';
class LocateDeviceDialog extends ConsumerWidget {
@@ -88,7 +90,10 @@ class LocateDeviceDialog extends ConsumerWidget {
SizedBox(width: SizeUtils.getByScreen(small: 8, big: 16)),
Expanded(
child: PrimaryButton(
onPressed: vm.locateDevice,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
vm.locateDevice();
},
text: context.translate(I18n.accept),
color: theme.getColorFor(ThemeCode.legacyPrimary),
height: SizeUtils.getByScreen(small: 38, big: 36),

View File

@@ -152,7 +152,10 @@ class _TakePictureSection extends ConsumerWidget {
big: EdgeInsets.symmetric(vertical: 10, horizontal: 25),
),
child: PrimaryButton(
onPressed: vm.takePicture,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
vm.takePicture();
},
text: context.translate(I18n.takePicture),
color: theme.getColorFor(ThemeCode.legacyPrimary),
height: SizeUtils.getByScreen(small: 36, big: 35),

View File

@@ -33,6 +33,7 @@ class RemoteConnectionScreen extends ConsumerWidget {
if (cameraEnabled) ...[
_SectionButton(
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
Navigator.push(
context,
MaterialPageRoute(
@@ -49,6 +50,7 @@ class RemoteConnectionScreen extends ConsumerWidget {
],
_SectionButton(
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
showDialog(
context: context,
builder: (context) => Dialog(child: SpyCallDialog()),

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_model.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_state.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -76,9 +77,15 @@ class SpyCallDialog extends ConsumerWidget {
),
),
SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)),
_PhoneSection(onSubmit: vm.call),
_PhoneSection(onSubmit: () {
if (!guardDeviceCommand(context, ref)) return;
vm.call();
}),
SizedBox(height: SizeUtils.getByScreen(small: 28, big: 27)),
_CallSection(onPressed: vm.call),
_CallSection(onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
vm.call();
}),
],
),
);

View File

@@ -104,8 +104,10 @@ class _ScheduledActivitiesScreenState
shape: const CircleBorder(),
child: InkWell(
customBorder: const CircleBorder(),
onTap: () =>
showActivityFormSheet(context, weekDay: _selectedWeekDay),
onTap: () {
if (!guardDeviceCommand(context, ref)) return;
showActivityFormSheet(context, weekDay: _selectedWeekDay);
},
child: SizedBox(
width: SizeUtils.getByScreen(small: 48, big: 46),
height: SizeUtils.getByScreen(small: 48, big: 46),

View File

@@ -99,6 +99,7 @@ class _ActivityFormSheetState extends ConsumerState<ActivityFormSheet> {
void _submit() {
if (!_isFormValid) return;
if (!guardDeviceCommand(context, ref)) return;
final name = _nameController.text.trim();
final period = _buildPeriod();

View File

@@ -1,6 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -155,14 +156,14 @@ class _TimelineDotAndLine extends StatelessWidget {
}
}
class _ActivityTimelineCard extends StatelessWidget {
class _ActivityTimelineCard extends ConsumerWidget {
final ScheduledActivityEntity activity;
final ThemePort theme;
const _ActivityTimelineCard({required this.activity, required this.theme});
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
return Container(
margin: EdgeInsets.only(bottom: SizeUtils.getByScreen(small: 10, big: 8)),
padding: SizeUtils.getByScreen(
@@ -187,7 +188,10 @@ class _ActivityTimelineCard extends StatelessWidget {
),
),
IconButton(
onPressed: () => showActivityFormSheet(context, activity: activity),
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
showActivityFormSheet(context, activity: activity);
},
icon: Icon(
Icons.edit_outlined,
color: theme.getColorFor(ThemeCode.legacyPrimary),
@@ -198,6 +202,7 @@ class _ActivityTimelineCard extends StatelessWidget {
),
IconButton(
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
showDialog(
context: context,
builder: (_) => Dialog(

View File

@@ -81,7 +81,10 @@ class VolumeControlScreen extends ConsumerWidget {
child: state.isLoading
? const Center(child: CircularProgressIndicator())
: PrimaryButton(
onPressed: vm.submit,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
vm.submit();
},
text: context.translate(I18n.volumeSend),
color: primaryColor,
),

View File

@@ -2,6 +2,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:latlong2/latlong.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:location/src/core/domain/entities/frequent_place_entity.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -74,6 +75,7 @@ class _FrequentPlaceSheetState extends ConsumerState<_FrequentPlaceSheet> {
Future<void> _submit() async {
if (!_canSave) return;
if (!guardDeviceCommand(context, ref)) return;
final vm = ref.read(locationViewModelProvider.notifier);
final bool success;

View File

@@ -2,6 +2,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:latlong2/latlong.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:location/src/core/domain/entities/geofence_entity.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -90,6 +91,7 @@ class _GeofenceSheetState extends ConsumerState<_GeofenceSheet> {
Future<void> _submit() async {
if (!_canSave) return;
if (!guardDeviceCommand(context, ref)) return;
final vm = ref.read(locationViewModelProvider.notifier);
final description = _descriptionController.text.trim();

View File

@@ -91,6 +91,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
_followTimer?.cancel();
final frequency = widget.selectedDevice?.settings.frequency ?? 60;
_followTimer = Timer.periodic(Duration(seconds: frequency), (_) {
if (ref.read(selectedDeviceProvider).value?.isDisconnected ?? true) return;
widget.onRefreshPosition();
});
}
@@ -236,6 +237,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
}
Future<void> _updateFrequency(int frequency) async {
if (!guardDeviceCommand(context, ref)) return;
final success = await ref
.read(locationViewModelProvider.notifier)
.updateLocationFrequency(frequency: frequency);
@@ -263,6 +265,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
}
void _confirmPlacement() {
if (!guardDeviceCommand(context, ref)) return;
final center = _mapController.camera.center;
final mapState = ref.read(locationMapViewModelProvider);
@@ -287,6 +290,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
}
void _confirmRadius() {
if (!guardDeviceCommand(context, ref)) return;
final mapState = ref.read(locationMapViewModelProvider);
final point = mapState.previewPoint!;
final radius = mapState.previewRadius;
@@ -411,6 +415,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
}
void _onEditFrequentPlace(FrequentPlaceEntity fp) {
if (!guardDeviceCommand(context, ref)) return;
_vm.clearSelectedFrequentPlace();
showNameInputSheet(
context,
@@ -669,6 +674,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
onAddFrequentPlace: () => _vm.startPlacing(PlacingMode.frequentPlace),
onShareTap: _shareLocation,
onRefreshTap: () {
if (!guardDeviceCommand(context, ref)) return;
unawaited(
ref.read(sfTrackingProvider).legacyLocationMapRefreshTapped(),
);
@@ -682,6 +688,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
onCenterTap: _centerOnDevice,
onToggleFollow: () {
final willActivate = !mapState.isFollowing;
if (willActivate && !guardDeviceCommand(context, ref)) return;
_vm.toggleFollowing();
unawaited(
ref
@@ -714,8 +721,12 @@ class _LocationMapState extends ConsumerState<LocationMap>
child: GeofenceInfoCard(
geofence: mapState.selectedGeofence!,
onClose: _vm.clearSelectedGeofence,
onEdit: () => _vm.startEditingGeofence(mapState.selectedGeofence!),
onEdit: () {
if (!guardDeviceCommand(context, ref)) return;
_vm.startEditingGeofence(mapState.selectedGeofence!);
},
onDelete: () {
if (!guardDeviceCommand(context, ref)) return;
final id = mapState.selectedGeofence!.id;
_vm.clearSelectedGeofence();
ref
@@ -732,6 +743,7 @@ class _LocationMapState extends ConsumerState<LocationMap>
onClose: _vm.clearSelectedFrequentPlace,
onEdit: () => _onEditFrequentPlace(mapState.selectedFrequentPlace!),
onDelete: () {
if (!guardDeviceCommand(context, ref)) return;
final id = mapState.selectedFrequentPlace!.id;
_vm.clearSelectedFrequentPlace();
ref

View File

@@ -1,6 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:navigation/navigation.dart';
import 'package:settings/src/features/alarm/domain/entities/alarm_entity.dart';
import 'package:settings/src/features/alarm/presentation/state/alarm_view_model.dart';
@@ -91,7 +92,10 @@ class AlarmScreen extends ConsumerWidget {
shape: BoxShape.circle,
),
child: IconButton(
onPressed: () => _openForm(context, vm),
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
_openForm(context, vm);
},
icon: Icon(
Icons.add,
color: Colors.white,
@@ -122,7 +126,10 @@ class AlarmScreen extends ConsumerWidget {
child: isSaving
? const Center(child: CircularProgressIndicator())
: PrimaryButton(
onPressed: vm.save,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
vm.save();
},
text: context.translate(I18n.alarmSave),
color: primaryColor,
),

View File

@@ -79,7 +79,12 @@ class AlertsScreen extends ConsumerWidget {
footer: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: PrimaryButton(
onPressed: state.isSaving ? null : () => vm.save(),
onPressed: state.isSaving
? null
: () {
if (!guardDeviceCommand(context, ref)) return;
vm.save();
},
text: state.isSaving ? '...' : context.translate(I18n.save),
color: theme.getColorFor(ThemeCode.legacyPrimary),
),

View File

@@ -99,7 +99,10 @@ class BatteryScreen extends ConsumerWidget {
activeTrackColor: primaryColor,
onChanged: state.isSaving
? null
: (value) => vm.toggleNightMode(value),
: (value) {
if (!guardDeviceCommand(context, ref)) return;
vm.toggleNightMode(value);
},
),
],
),

View File

@@ -1,6 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -83,7 +84,10 @@ class BlockPhoneScreen extends ConsumerWidget {
shape: BoxShape.circle,
),
child: IconButton(
onPressed: () => showAddContactSheet(context),
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
showAddContactSheet(context);
},
icon: Icon(
Icons.add,
color: Colors.white,
@@ -218,6 +222,7 @@ class _ContactList extends ConsumerWidget {
int index,
String name,
) {
if (!guardDeviceCommand(context, ref)) return;
final theme = ref.read(themePortProvider);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);

View File

@@ -76,7 +76,12 @@ class DisableFunctionsScreen extends ConsumerWidget {
footer: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: PrimaryButton(
onPressed: state.isSaving ? null : () => vm.save(),
onPressed: state.isSaving
? null
: () {
if (!guardDeviceCommand(context, ref)) return;
vm.save();
},
text: state.isSaving ? '...' : context.translate(I18n.save),
color: primaryColor,
),

View File

@@ -110,7 +110,10 @@ class _SaveSection extends ConsumerWidget {
child: isLoading
? const Center(child: CircularProgressIndicator())
: PrimaryButton(
onPressed: vm.submit,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
vm.submit();
},
text: context.translate(I18n.save),
color: theme.getColorFor(ThemeCode.legacyPrimary),
),

View File

@@ -52,6 +52,7 @@ class _OptionsSection extends ConsumerWidget {
subtitle: context.translate(I18n.remoteTurnOffMessage),
icon: Icons.settings_power_outlined,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
showDialog(
context: context,
builder: (context) => Dialog(
@@ -95,6 +96,7 @@ class _OptionsSection extends ConsumerWidget {
subtitle: context.translate(I18n.remoteRestartMessage),
icon: Icons.refresh_outlined,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
showDialog(
context: context,
builder: (context) => Dialog(
@@ -116,6 +118,7 @@ class _OptionsSection extends ConsumerWidget {
subtitle: context.translate(I18n.remoteFactoryResetMessage),
icon: Icons.restart_alt_outlined,
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
showDialog(
context: context,
builder: (context) => Dialog(

View File

@@ -178,6 +178,7 @@ class _SaveSection extends ConsumerWidget {
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: PrimaryButton(
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
vm.submit();
},
text: context.translate(I18n.save),

View File

@@ -94,7 +94,12 @@ class TimezoneScreen extends ConsumerWidget {
footer: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: PrimaryButton(
onPressed: state.isSaving ? null : () => vm.save(),
onPressed: state.isSaving
? null
: () {
if (!guardDeviceCommand(context, ref)) return;
vm.save();
},
text: state.isSaving ? '...' : context.translate(I18n.save),
color: primaryColor,
),

View File

@@ -1,6 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
@@ -83,7 +84,10 @@ class WifiSettingsScreen extends ConsumerWidget {
shape: BoxShape.circle,
),
child: IconButton(
onPressed: () => showAddWifiNetworkSheet(context),
onPressed: () {
if (!guardDeviceCommand(context, ref)) return;
showAddWifiNetworkSheet(context);
},
icon: Icon(
Icons.add,
color: Colors.white,
@@ -212,6 +216,7 @@ class _NetworkList extends ConsumerWidget {
}
void _confirmDelete(BuildContext context, WidgetRef ref, dynamic network) {
if (!guardDeviceCommand(context, ref)) return;
final theme = ref.read(themePortProvider);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);

View File

@@ -17,3 +17,4 @@ export 'src/providers/devices_repository_provider.dart';
export 'src/providers/legacy_devices_provider.dart';
export 'src/data/datasources/device_settings_update_datasource.dart';
export 'src/providers/device_settings_update_provider.dart';
export 'src/utils/device_command_guard.dart';

View File

@@ -0,0 +1,20 @@
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';
import 'package:sf_shared/sf_shared.dart';
import '../providers/selected_device_provider.dart';
bool guardDeviceCommand(BuildContext context, WidgetRef ref) {
final device = ref.read(selectedDeviceProvider).value;
if (device == null || device.isDisconnected) {
showTopSnackbar(
context,
message: context.translate(I18n.errorDeviceDisconnected),
type: MessageType.error,
);
return false;
}
return true;
}

View File

@@ -674,6 +674,7 @@
"errorContactsMin": "Das Gerät muss mindestens einen Kontakt haben",
"errorContactsMax": "Maximale Kontaktanzahl für dieses Gerät erreicht",
"errorPositions": "Positionen konnten nicht geladen werden",
"errorDeviceDisconnected": "Die Uhr ist getrennt und kann keine Befehle empfangen",
"errorSosContactsMax": "Maximale SOS-Kontaktanzahl für dieses Gerät erreicht",
"customBackground": "Benutzerdefiniertes Hintergrundbild",
"backgroundImageDescription": "Legen Sie ein Foto als benutzerdefinierten Bildschirmschoner für das Gerät fest",

View File

@@ -826,6 +826,7 @@
"errorContactsMin": "The device must have at least one contact",
"errorContactsMax": "Maximum contacts reached for this device",
"errorPositions": "Could not load positions",
"errorDeviceDisconnected": "The watch is disconnected and cannot receive commands",
"errorSosContactsMax": "Maximum SOS contacts reached for this device",
"customBackground": "Custom background image",
"backgroundImageDescription": "Set a photo as a custom screensaver for the device",

View File

@@ -827,6 +827,7 @@
"errorContactsMin": "El dispositivo debe tener al menos un contacto",
"errorContactsMax": "Se ha alcanzado el máximo de contactos para este dispositivo",
"errorPositions": "No se pudieron cargar las posiciones",
"errorDeviceDisconnected": "El reloj está desconectado y no puede recibir comandos",
"errorSosContactsMax": "Se ha alcanzado el máximo de contactos SOS para este dispositivo",
"customBackground": "Fondo de pantalla personalizado",
"backgroundImageDescription": "Configura una foto como protector de pantalla exclusivo para el dispositivo",

View File

@@ -674,6 +674,7 @@
"errorContactsMin": "L'appareil doit avoir au moins un contact",
"errorContactsMax": "Nombre maximum de contacts atteint pour cet appareil",
"errorPositions": "Impossible de charger les positions",
"errorDeviceDisconnected": "La montre est déconnectée et ne peut pas recevoir de commandes",
"errorSosContactsMax": "Nombre maximum de contacts SOS atteint pour cet appareil",
"customBackground": "Image de fond personnalisée",
"backgroundImageDescription": "Définissez une photo comme écran de veille personnalisé pour l'appareil",

View File

@@ -674,6 +674,7 @@
"errorContactsMin": "Il dispositivo deve avere almeno un contatto",
"errorContactsMax": "Numero massimo di contatti raggiunto per questo dispositivo",
"errorPositions": "Impossibile caricare le posizioni",
"errorDeviceDisconnected": "L'orologio è disconnesso e non può ricevere comandi",
"errorSosContactsMax": "Numero massimo di contatti SOS raggiunto per questo dispositivo",
"customBackground": "Immagine di sfondo personalizzata",
"backgroundImageDescription": "Imposta una foto come screensaver personalizzato per il dispositivo",

View File

@@ -674,6 +674,7 @@
"errorContactsMin": "O dispositivo deve ter pelo menos um contacto",
"errorContactsMax": "Número máximo de contactos atingido para este dispositivo",
"errorPositions": "Não foi possível carregar as posições",
"errorDeviceDisconnected": "O relógio está desconectado e não pode receber comandos",
"errorSosContactsMax": "Número máximo de contactos SOS atingido para este dispositivo",
"customBackground": "Imagem de fundo personalizada",
"backgroundImageDescription": "Defina uma foto como protetor de ecrã personalizado para o dispositivo",

View File

@@ -378,6 +378,7 @@ class I18n {
static const String errorCall = 'errorCall';
static const String errorContactsMax = 'errorContactsMax';
static const String errorPositions = 'errorPositions';
static const String errorDeviceDisconnected = 'errorDeviceDisconnected';
static const String errorSosContactsMax = 'errorSosContactsMax';
static const String errorContactsMin = 'errorContactsMin';
static const String errorDisableFunctions = 'errorDisableFunctions';

View File

@@ -37,3 +37,7 @@ abstract class DeviceEntity with _$DeviceEntity {
String? updatedAt,
}) = _DeviceEntity;
}
extension DeviceEntityFlags on DeviceEntity {
bool get isDisconnected => flags['isDisconnect'] == true;
}