refactor(do-not-disturb): move data layer to core, remove delete action

This commit is contained in:
2026-04-16 21:26:07 +02:00
parent 297fa8241a
commit 769e8fea27
20 changed files with 73 additions and 54 deletions

View File

@@ -21,6 +21,21 @@ class ControlPanelScreen extends ConsumerWidget {
final theme = ref.watch(themePortProvider);
final asyncState = ref.watch(controlPanelViewModelProvider);
ref.listen(
controlPanelViewModelProvider.select(
(async) => async.value?.positionsError ?? false,
),
(_, hasError) {
if (hasError) {
showTopSnackbar(
context,
message: context.translate(I18n.errorPositions),
type: MessageType.error,
);
}
},
);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
body: asyncState.when(

View File

@@ -27,14 +27,7 @@ class ControlPanelViewModel extends AsyncNotifier<ControlPanelViewState> {
final selected = await ref.watch(selectedDeviceProvider.future);
final positionLists = await Future.wait<List<PositionEntity>>(
devices.map(
(d) => _controlPanelRepository.getLatestPositions(
deviceId: d.identificator,
),
),
);
final latestPositions = _pickLatest(positionLists);
final (latestPositions, hadError) = await _fetchPositions(devices);
final selectedPosition = selected != null
? latestPositions
.where((p) => p.deviceIdentificator == selected.identificator)
@@ -46,6 +39,7 @@ class ControlPanelViewModel extends AsyncNotifier<ControlPanelViewState> {
selectedDevice: selected,
positions: latestPositions,
selectedPosition: selectedPosition,
positionsError: hadError,
);
}
@@ -53,16 +47,9 @@ class ControlPanelViewModel extends AsyncNotifier<ControlPanelViewState> {
final current = state.value;
if (current == null || current.devices.isEmpty) return;
final positionLists = await Future.wait<List<PositionEntity>>(
current.devices.map(
(d) => _controlPanelRepository.getLatestPositions(
deviceId: d.identificator,
),
),
);
final (latestPositions, hadError) = await _fetchPositions(current.devices);
if (!ref.mounted) return;
final latestPositions = _pickLatest(positionLists);
final selectedPosition = current.selectedDevice != null
? latestPositions
.where(
@@ -77,12 +64,30 @@ class ControlPanelViewModel extends AsyncNotifier<ControlPanelViewState> {
current.copyWith(
positions: latestPositions,
selectedPosition: selectedPosition,
positionsError: hadError,
),
);
unawaited(_tracking.legacyControlPanelPositionsRefreshed());
}
Future<(List<PositionEntity>, bool)> _fetchPositions(
List<DeviceEntity> devices,
) async {
var hadError = false;
final positionLists = await Future.wait<List<PositionEntity>>(
devices.map(
(device) => _controlPanelRepository
.getLatestPositions(deviceId: device.identificator)
.catchError((_) {
hadError = true;
return <PositionEntity>[];
}),
),
);
return (_pickLatest(positionLists), hadError);
}
Future<void> setSelectedDevice(DeviceEntity device) async {
final current = state.value;
if (current == null) return;

View File

@@ -11,5 +11,6 @@ abstract class ControlPanelViewState with _$ControlPanelViewState {
DeviceEntity? selectedDevice,
@Default([]) List<PositionEntity> positions,
PositionEntity? selectedPosition,
@Default(false) bool positionsError,
}) = _ControlPanelViewState;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$ControlPanelViewState {
List<DeviceEntity> get devices; DeviceEntity? get selectedDevice; List<PositionEntity> get positions; PositionEntity? get selectedPosition;
List<DeviceEntity> get devices; DeviceEntity? get selectedDevice; List<PositionEntity> get positions; PositionEntity? get selectedPosition; bool get positionsError;
/// Create a copy of ControlPanelViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $ControlPanelViewStateCopyWith<ControlPanelViewState> get copyWith => _$ControlP
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ControlPanelViewState&&const DeepCollectionEquality().equals(other.devices, devices)&&(identical(other.selectedDevice, selectedDevice) || other.selectedDevice == selectedDevice)&&const DeepCollectionEquality().equals(other.positions, positions)&&(identical(other.selectedPosition, selectedPosition) || other.selectedPosition == selectedPosition));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ControlPanelViewState&&const DeepCollectionEquality().equals(other.devices, devices)&&(identical(other.selectedDevice, selectedDevice) || other.selectedDevice == selectedDevice)&&const DeepCollectionEquality().equals(other.positions, positions)&&(identical(other.selectedPosition, selectedPosition) || other.selectedPosition == selectedPosition)&&(identical(other.positionsError, positionsError) || other.positionsError == positionsError));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(devices),selectedDevice,const DeepCollectionEquality().hash(positions),selectedPosition);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(devices),selectedDevice,const DeepCollectionEquality().hash(positions),selectedPosition,positionsError);
@override
String toString() {
return 'ControlPanelViewState(devices: $devices, selectedDevice: $selectedDevice, positions: $positions, selectedPosition: $selectedPosition)';
return 'ControlPanelViewState(devices: $devices, selectedDevice: $selectedDevice, positions: $positions, selectedPosition: $selectedPosition, positionsError: $positionsError)';
}
@@ -45,7 +45,7 @@ abstract mixin class $ControlPanelViewStateCopyWith<$Res> {
factory $ControlPanelViewStateCopyWith(ControlPanelViewState value, $Res Function(ControlPanelViewState) _then) = _$ControlPanelViewStateCopyWithImpl;
@useResult
$Res call({
List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition
List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition, bool positionsError
});
@@ -62,13 +62,14 @@ class _$ControlPanelViewStateCopyWithImpl<$Res>
/// Create a copy of ControlPanelViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? devices = null,Object? selectedDevice = freezed,Object? positions = null,Object? selectedPosition = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? devices = null,Object? selectedDevice = freezed,Object? positions = null,Object? selectedPosition = freezed,Object? positionsError = null,}) {
return _then(_self.copyWith(
devices: null == devices ? _self.devices : devices // ignore: cast_nullable_to_non_nullable
as List<DeviceEntity>,selectedDevice: freezed == selectedDevice ? _self.selectedDevice : selectedDevice // ignore: cast_nullable_to_non_nullable
as DeviceEntity?,positions: null == positions ? _self.positions : positions // ignore: cast_nullable_to_non_nullable
as List<PositionEntity>,selectedPosition: freezed == selectedPosition ? _self.selectedPosition : selectedPosition // ignore: cast_nullable_to_non_nullable
as PositionEntity?,
as PositionEntity?,positionsError: null == positionsError ? _self.positionsError : positionsError // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of ControlPanelViewState
@@ -177,10 +178,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition, bool positionsError)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ControlPanelViewState() when $default != null:
return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selectedPosition);case _:
return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selectedPosition,_that.positionsError);case _:
return orElse();
}
@@ -198,10 +199,10 @@ return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selecte
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition, bool positionsError) $default,) {final _that = this;
switch (_that) {
case _ControlPanelViewState():
return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selectedPosition);case _:
return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selectedPosition,_that.positionsError);case _:
throw StateError('Unexpected subclass');
}
@@ -218,10 +219,10 @@ return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selecte
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition, bool positionsError)? $default,) {final _that = this;
switch (_that) {
case _ControlPanelViewState() when $default != null:
return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selectedPosition);case _:
return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selectedPosition,_that.positionsError);case _:
return null;
}
@@ -233,7 +234,7 @@ return $default(_that.devices,_that.selectedDevice,_that.positions,_that.selecte
class _ControlPanelViewState implements ControlPanelViewState {
const _ControlPanelViewState({final List<DeviceEntity> devices = const [], this.selectedDevice, final List<PositionEntity> positions = const [], this.selectedPosition}): _devices = devices,_positions = positions;
const _ControlPanelViewState({final List<DeviceEntity> devices = const [], this.selectedDevice, final List<PositionEntity> positions = const [], this.selectedPosition, this.positionsError = false}): _devices = devices,_positions = positions;
final List<DeviceEntity> _devices;
@@ -252,6 +253,7 @@ class _ControlPanelViewState implements ControlPanelViewState {
}
@override final PositionEntity? selectedPosition;
@override@JsonKey() final bool positionsError;
/// Create a copy of ControlPanelViewState
/// with the given fields replaced by the non-null parameter values.
@@ -263,16 +265,16 @@ _$ControlPanelViewStateCopyWith<_ControlPanelViewState> get copyWith => __$Contr
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ControlPanelViewState&&const DeepCollectionEquality().equals(other._devices, _devices)&&(identical(other.selectedDevice, selectedDevice) || other.selectedDevice == selectedDevice)&&const DeepCollectionEquality().equals(other._positions, _positions)&&(identical(other.selectedPosition, selectedPosition) || other.selectedPosition == selectedPosition));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ControlPanelViewState&&const DeepCollectionEquality().equals(other._devices, _devices)&&(identical(other.selectedDevice, selectedDevice) || other.selectedDevice == selectedDevice)&&const DeepCollectionEquality().equals(other._positions, _positions)&&(identical(other.selectedPosition, selectedPosition) || other.selectedPosition == selectedPosition)&&(identical(other.positionsError, positionsError) || other.positionsError == positionsError));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_devices),selectedDevice,const DeepCollectionEquality().hash(_positions),selectedPosition);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_devices),selectedDevice,const DeepCollectionEquality().hash(_positions),selectedPosition,positionsError);
@override
String toString() {
return 'ControlPanelViewState(devices: $devices, selectedDevice: $selectedDevice, positions: $positions, selectedPosition: $selectedPosition)';
return 'ControlPanelViewState(devices: $devices, selectedDevice: $selectedDevice, positions: $positions, selectedPosition: $selectedPosition, positionsError: $positionsError)';
}
@@ -283,7 +285,7 @@ abstract mixin class _$ControlPanelViewStateCopyWith<$Res> implements $ControlPa
factory _$ControlPanelViewStateCopyWith(_ControlPanelViewState value, $Res Function(_ControlPanelViewState) _then) = __$ControlPanelViewStateCopyWithImpl;
@override @useResult
$Res call({
List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition
List<DeviceEntity> devices, DeviceEntity? selectedDevice, List<PositionEntity> positions, PositionEntity? selectedPosition, bool positionsError
});
@@ -300,13 +302,14 @@ class __$ControlPanelViewStateCopyWithImpl<$Res>
/// Create a copy of ControlPanelViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? devices = null,Object? selectedDevice = freezed,Object? positions = null,Object? selectedPosition = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? devices = null,Object? selectedDevice = freezed,Object? positions = null,Object? selectedPosition = freezed,Object? positionsError = null,}) {
return _then(_ControlPanelViewState(
devices: null == devices ? _self._devices : devices // ignore: cast_nullable_to_non_nullable
as List<DeviceEntity>,selectedDevice: freezed == selectedDevice ? _self.selectedDevice : selectedDevice // ignore: cast_nullable_to_non_nullable
as DeviceEntity?,positions: null == positions ? _self._positions : positions // ignore: cast_nullable_to_non_nullable
as List<PositionEntity>,selectedPosition: freezed == selectedPosition ? _self.selectedPosition : selectedPosition // ignore: cast_nullable_to_non_nullable
as PositionEntity?,
as PositionEntity?,positionsError: null == positionsError ? _self.positionsError : positionsError // ignore: cast_nullable_to_non_nullable
as bool,
));
}

View File

@@ -1,4 +1,4 @@
import '../../domain/do_not_disturb_period.dart';
import '../../../features/do_not_disturb/domain/do_not_disturb_period.dart';
abstract class DoNotDisturbRemoteDatasource {
Future<DoNotDisturbSchedule> getSchedule({required String identificator});

View File

@@ -1,6 +1,6 @@
import 'package:sf_infrastructure/sf_infrastructure.dart';
import '../../domain/do_not_disturb_period.dart';
import '../../../features/do_not_disturb/domain/do_not_disturb_period.dart';
import '../models/do_not_disturb_response_model.dart';
import 'do_not_disturb_remote_datasource.dart';

View File

@@ -2,8 +2,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get_it/get_it.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import '../data/datasource/do_not_disturb_remote_datasource.dart';
import '../data/datasource/do_not_disturb_remote_datasource_impl.dart';
import '../data/datasources/do_not_disturb_remote_datasource.dart';
import '../data/datasources/do_not_disturb_remote_datasource_impl.dart';
final doNotDisturbRemoteDatasourceProvider =
Provider<DoNotDisturbRemoteDatasource>(

View File

@@ -96,7 +96,6 @@ class DoNotDisturbScreen extends ConsumerWidget {
entry.key,
entry.value,
),
onDelete: () => vm.removePeriod(entry.key),
),
);
}),

View File

@@ -4,9 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_tracking/sf_tracking.dart';
import '../../data/datasource/do_not_disturb_remote_datasource.dart';
import '../../../../core/data/datasources/do_not_disturb_remote_datasource.dart';
import '../../domain/do_not_disturb_period.dart';
import '../../providers/do_not_disturb_providers.dart';
import '../../../../core/providers/do_not_disturb_providers.dart';
import 'do_not_disturb_view_state.dart';
final doNotDisturbViewModelProvider =

View File

@@ -9,13 +9,11 @@ import 'week_day_row.dart';
class DoNotDisturbPeriodCard extends ConsumerWidget {
final DoNotDisturbPeriod period;
final VoidCallback onEdit;
final VoidCallback onDelete;
const DoNotDisturbPeriodCard({
super.key,
required this.period,
required this.onEdit,
required this.onDelete,
});
@override
@@ -60,15 +58,6 @@ class DoNotDisturbPeriodCard extends ConsumerWidget {
color: primaryColor,
),
),
SizedBox(width: SizeUtils.getByScreen(small: 12, big: 10)),
GestureDetector(
onTap: onDelete,
child: Icon(
Icons.delete_outline,
size: SizeUtils.getByScreen(small: 20, big: 18),
color: Colors.red.shade400,
),
),
],
),
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 6)),

View File

@@ -673,6 +673,7 @@
"errorPedometer": "Der Schrittzähler konnte nicht aktualisiert werden",
"errorContactsMin": "Das Gerät muss mindestens einen Kontakt haben",
"errorContactsMax": "Maximale Kontaktanzahl für dieses Gerät erreicht",
"errorPositions": "Positionen konnten nicht geladen werden",
"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

@@ -825,6 +825,7 @@
"errorPedometer": "Could not update pedometer",
"errorContactsMin": "The device must have at least one contact",
"errorContactsMax": "Maximum contacts reached for this device",
"errorPositions": "Could not load positions",
"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

@@ -826,6 +826,7 @@
"errorPedometer": "No se pudo actualizar el podómetro",
"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",
"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

@@ -673,6 +673,7 @@
"errorPedometer": "Impossible de mettre à jour le podomètre",
"errorContactsMin": "L'appareil doit avoir au moins un contact",
"errorContactsMax": "Nombre maximum de contacts atteint pour cet appareil",
"errorPositions": "Impossible de charger les positions",
"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

@@ -673,6 +673,7 @@
"errorPedometer": "Impossibile aggiornare il contapassi",
"errorContactsMin": "Il dispositivo deve avere almeno un contatto",
"errorContactsMax": "Numero massimo di contatti raggiunto per questo dispositivo",
"errorPositions": "Impossibile caricare le posizioni",
"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

@@ -673,6 +673,7 @@
"errorPedometer": "Não foi possível atualizar o pedómetro",
"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",
"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

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