From fb281caf992430861b56608636219e6b10ad825f Mon Sep 17 00:00:00 2001 From: JulianAlcala Date: Wed, 22 Apr 2026 21:56:07 +0200 Subject: [PATCH] refactor(device_management): migrate background_image to Riverpod --- .../presentation/background_image_screen.dart | 143 +++++---- .../background_image_controller.dart | 79 +++++ .../background_image_controller.g.dart | 56 ++++ .../background_image_photos_provider.dart | 10 + .../background_image_photos_provider.g.dart | 52 ++++ .../state/background_image_view_model.dart | 156 ---------- .../state/background_image_view_state.dart | 20 -- .../background_image_view_state.freezed.dart | 292 ------------------ .../background_image_controller_test.dart | 103 ++++++ 9 files changed, 377 insertions(+), 534 deletions(-) create mode 100644 modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_controller.dart create mode 100644 modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_controller.g.dart create mode 100644 modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_photos_provider.dart create mode 100644 modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_photos_provider.g.dart delete mode 100644 modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_model.dart delete mode 100644 modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_state.dart delete mode 100644 modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_state.freezed.dart create mode 100644 modules/legacy/modules/device_management/test/features/background_image/background_image_controller_test.dart diff --git a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/background_image_screen.dart b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/background_image_screen.dart index 2c1c8cc8..f765616d 100644 --- a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/background_image_screen.dart +++ b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/background_image_screen.dart @@ -1,15 +1,16 @@ import 'package:design_system/design_system.dart'; -import 'package:legacy_theme/legacy_theme.dart'; +import 'package:device_management/src/features/background_image/presentation/providers/background_image_controller.dart'; +import 'package:device_management/src/features/background_image/presentation/providers/background_image_photos_provider.dart'; +import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:legacy_device_state/legacy_device_state.dart'; +import 'package:legacy_theme/legacy_theme.dart'; import 'package:navigation/navigation.dart'; import 'package:sf_localizations/sf_localizations.dart'; +import 'package:sf_shared/sf_shared.dart'; import 'package:utils/utils.dart'; -import 'state/background_image_view_model.dart'; -import 'state/background_image_view_state.dart'; - class BackgroundImageScreen extends ConsumerWidget { final NavigationContract navigationContract; @@ -18,45 +19,32 @@ class BackgroundImageScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final primaryColor = context.sfColors.legacyPrimary; + final device = ref.watch(selectedDeviceProvider).value; + final photosAsync = ref.watch(backgroundImagePhotosProvider); + final isSaving = ref + .watch(backgroundImageControllerProvider.select((s) => s.isLoading)); - final state = ref.watch(backgroundImageViewModelProvider); - final vm = ref.read(backgroundImageViewModelProvider.notifier); - - ref.listen(backgroundImageViewModelProvider.select((s) => s.errorEvent), ( - previous, - next, - ) { - if (next != null) { - final message = switch (next) { - BackgroundImageErrorEvent.load => context.translate( - I18n.errorBackgroundImageLoad, - ), - BackgroundImageErrorEvent.upload => context.translate( - I18n.errorBackgroundImageUpload, - ), - BackgroundImageErrorEvent.set => context.translate( - I18n.errorBackgroundImageSet, - ), + ref.listen(backgroundImageControllerProvider, (prev, next) async { + if (prev == null || !prev.isLoading || next.isLoading) return; + if (next.hasError) { + final action = + ref.read(backgroundImageControllerProvider.notifier).lastAction; + final key = switch (action) { + BackgroundImageAction.uploaded => I18n.errorBackgroundImageUpload, + BackgroundImageAction.backgroundSet => I18n.errorBackgroundImageSet, + null => I18n.errorGeneric, }; - showTopSnackbar(context, message: message, type: MessageType.error); - } - }); - - ref.listen(backgroundImageViewModelProvider.select((s) => s.successEvent), ( - previous, - next, - ) { - if (next != null) { - final message = switch (next) { - BackgroundImageSuccessEvent.uploaded => context.translate( - I18n.backgroundImageUploaded, - ), - BackgroundImageSuccessEvent.backgroundSet => context.translate( - I18n.backgroundImageSet, - ), - }; - showTopSnackbar(context, message: message, type: MessageType.success); + await showErrorDialog(context, key); + return; } + final action = + ref.read(backgroundImageControllerProvider.notifier).lastAction; + final key = switch (action) { + BackgroundImageAction.uploaded => I18n.backgroundImageUploaded, + BackgroundImageAction.backgroundSet => I18n.backgroundImageSet, + null => I18n.deviceUpdatedSuccess, + }; + await showSuccessDialog(context, key); }); return Scaffold( @@ -95,11 +83,14 @@ class BackgroundImageScreen extends ConsumerWidget { shape: BoxShape.circle, ), child: IconButton( - onPressed: state.isSaving + onPressed: isSaving || device == null ? null : () async { if (!await guardDeviceCommand(context, ref)) return; - vm.uploadPhoto(); + if (!context.mounted) return; + ref + .read(backgroundImageControllerProvider.notifier) + .uploadPhoto(device: device); }, icon: Icon( Icons.add_photo_alternate_outlined, @@ -113,26 +104,43 @@ class BackgroundImageScreen extends ConsumerWidget { ), body: SafeArea( top: false, - child: state.isLoading - ? const Center(child: CircularProgressIndicator()) - : state.isSaving - ? const Center(child: CircularProgressIndicator()) - : state.photos.isEmpty - ? _EmptyState( + child: photosAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (_, __) => Center( + child: Text(context.translate(I18n.errorBackgroundImageLoad)), + ), + data: (photos) { + if (isSaving) { + return const Center(child: CircularProgressIndicator()); + } + if (photos.isEmpty) { + return _EmptyState( onUpload: () async { + if (device == null) return; if (!await guardDeviceCommand(context, ref)) return; - vm.uploadPhoto(); + if (!context.mounted) return; + ref + .read(backgroundImageControllerProvider.notifier) + .uploadPhoto(device: device); }, primaryColor: primaryColor, - ) - : _PhotoGrid( - state: state, - onPhotoTap: (id) async { - if (!await guardDeviceCommand(context, ref)) return; - vm.setAsBackground(id); - }, - primaryColor: primaryColor, - ), + ); + } + return _PhotoGrid( + photos: photos, + currentBackgroundId: device?.backgroundImageId, + primaryColor: primaryColor, + onPhotoTap: (id) async { + if (device == null) return; + if (!await guardDeviceCommand(context, ref)) return; + if (!context.mounted) return; + ref + .read(backgroundImageControllerProvider.notifier) + .setAsBackground(device: device, photoId: id); + }, + ); + }, + ), ), ); } @@ -176,14 +184,16 @@ class _EmptyState extends StatelessWidget { } class _PhotoGrid extends StatelessWidget { - final BackgroundImageViewState state; - final ValueChanged onPhotoTap; + final List photos; + final String? currentBackgroundId; final Color primaryColor; + final ValueChanged onPhotoTap; const _PhotoGrid({ - required this.state, - required this.onPhotoTap, + required this.photos, + required this.currentBackgroundId, required this.primaryColor, + required this.onPhotoTap, }); @override @@ -206,10 +216,10 @@ class _PhotoGrid extends StatelessWidget { crossAxisSpacing: 10, mainAxisSpacing: 10, ), - itemCount: state.photos.length, + itemCount: photos.length, itemBuilder: (context, index) { - final photo = state.photos[index]; - final isActive = state.currentBackgroundId == photo.id; + final photo = photos[index]; + final isActive = currentBackgroundId == photo.id; return GestureDetector( onTap: () => onPhotoTap(photo.id), child: Stack( @@ -232,7 +242,8 @@ class _PhotoGrid extends StatelessWidget { child: Icon( Icons.image_outlined, size: 48, - color: Theme.of(context).colorScheme.outline, + color: + Theme.of(context).colorScheme.outline, ), ), ), diff --git a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_controller.dart b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_controller.dart new file mode 100644 index 00000000..908321fa --- /dev/null +++ b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_controller.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:device_management/src/core/providers/background_image_repository_provider.dart'; +import 'package:device_management/src/features/background_image/presentation/providers/background_image_photos_provider.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:sf_shared/sf_shared.dart'; +import 'package:sf_tracking/sf_tracking.dart'; + +part 'background_image_controller.g.dart'; + +enum BackgroundImageAction { uploaded, backgroundSet } + +class BackgroundImageNoSelectionException implements Exception { + const BackgroundImageNoSelectionException(); +} + +@riverpod +class BackgroundImageController extends _$BackgroundImageController { + BackgroundImageAction? _lastAction; + + BackgroundImageAction? get lastAction => _lastAction; + + @override + FutureOr build() {} + + Future uploadPhoto({required DeviceEntity device}) async { + final picker = ImagePicker(); + final image = await picker.pickImage( + source: ImageSource.gallery, + maxWidth: 800, + imageQuality: 80, + ); + if (image == null) return; + + _lastAction = BackgroundImageAction.uploaded; + state = const AsyncLoading(); + state = await AsyncValue.guard(() async { + final photoId = await ref + .read(backgroundImageRepositoryProvider) + .uploadImage(path: image.path); + await ref + .read(backgroundImageRepositoryProvider) + .setBackgroundImage(deviceId: device.id, photoId: photoId); + await _refreshDevice(device); + ref.invalidate(backgroundImagePhotosProvider); + unawaited( + ref.read(sfTrackingProvider).legacyDeviceBackgroundImageUploaded(), + ); + }); + } + + Future setAsBackground({ + required DeviceEntity device, + required String photoId, + }) async { + _lastAction = BackgroundImageAction.backgroundSet; + state = const AsyncLoading(); + state = await AsyncValue.guard(() async { + await ref + .read(backgroundImageRepositoryProvider) + .setBackgroundImage(deviceId: device.id, photoId: photoId); + await _refreshDevice(device); + unawaited( + ref.read(sfTrackingProvider).legacyDeviceBackgroundImageChanged(), + ); + }); + } + + Future _refreshDevice(DeviceEntity current) async { + final devices = + await ref.read(sharedDevicesRepositoryProvider).getDevices(); + final updated = devices.firstWhere( + (d) => d.identificator == current.identificator, + orElse: () => current, + ); + ref.read(selectedDeviceProvider.notifier).setSelectedDevice(updated); + } +} diff --git a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_controller.g.dart b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_controller.g.dart new file mode 100644 index 00000000..d4494e0a --- /dev/null +++ b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_controller.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'background_image_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(BackgroundImageController) +const backgroundImageControllerProvider = BackgroundImageControllerProvider._(); + +final class BackgroundImageControllerProvider + extends $AsyncNotifierProvider { + const BackgroundImageControllerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'backgroundImageControllerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$backgroundImageControllerHash(); + + @$internal + @override + BackgroundImageController create() => BackgroundImageController(); +} + +String _$backgroundImageControllerHash() => + r'60faa9520c5534e915321f65d92119220c1ca1e5'; + +abstract class _$BackgroundImageController extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + void runBuild() { + build(); + final ref = this.ref as $Ref, void>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, void>, + AsyncValue, + Object?, + Object? + >; + element.handleValue(ref, null); + } +} diff --git a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_photos_provider.dart b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_photos_provider.dart new file mode 100644 index 00000000..f454bd8d --- /dev/null +++ b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_photos_provider.dart @@ -0,0 +1,10 @@ +import 'package:device_management/src/core/providers/background_image_repository_provider.dart'; +import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'background_image_photos_provider.g.dart'; + +@riverpod +Future> backgroundImagePhotos(Ref ref) async { + return ref.read(backgroundImageRepositoryProvider).getPhotos(); +} diff --git a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_photos_provider.g.dart b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_photos_provider.g.dart new file mode 100644 index 00000000..2db2e35c --- /dev/null +++ b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/providers/background_image_photos_provider.g.dart @@ -0,0 +1,52 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'background_image_photos_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(backgroundImagePhotos) +const backgroundImagePhotosProvider = BackgroundImagePhotosProvider._(); + +final class BackgroundImagePhotosProvider + extends + $FunctionalProvider< + AsyncValue>, + List, + FutureOr> + > + with + $FutureModifier>, + $FutureProvider> { + const BackgroundImagePhotosProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'backgroundImagePhotosProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$backgroundImagePhotosHash(); + + @$internal + @override + $FutureProviderElement> $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr> create(Ref ref) { + return backgroundImagePhotos(ref); + } +} + +String _$backgroundImagePhotosHash() => + r'25ebb7f6612d1b95435c4b59f66d035f5281ff8b'; diff --git a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_model.dart b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_model.dart deleted file mode 100644 index b7592628..00000000 --- a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_model.dart +++ /dev/null @@ -1,156 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:sf_shared/sf_shared.dart'; -import 'package:sf_tracking/sf_tracking.dart'; - -import '../../../../core/domain/repositories/background_image_repository.dart'; -import '../../../../core/providers/background_image_repository_provider.dart'; -import 'background_image_view_state.dart'; - -final backgroundImageViewModelProvider = - NotifierProvider.autoDispose< - BackgroundImageViewModel, - BackgroundImageViewState - >(BackgroundImageViewModel.new); - -class BackgroundImageViewModel extends Notifier { - late final BackgroundImageRepository _repository; - late final SharedDevicesRepository _devicesRepository; - late final SfTrackingRepository _tracking; - - @override - BackgroundImageViewState build() { - _repository = ref.read(backgroundImageRepositoryProvider); - _devicesRepository = ref.read(sharedDevicesRepositoryProvider); - _tracking = ref.read(sfTrackingProvider); - Future.microtask(_load); - return const BackgroundImageViewState(); - } - - Future _load() async { - try { - final device = ref.read(selectedDeviceProvider).value; - if (device == null) return; - - final photos = await _repository.getPhotos(); - if (!ref.mounted) return; - - state = state.copyWith( - photos: photos, - currentBackgroundId: device.backgroundImageId, - isLoading: false, - ); - } catch (e) { - if (!ref.mounted) return; - state = state.copyWith( - isLoading: false, - errorEvent: BackgroundImageErrorEvent.load, - ); - } - } - - Future reload() async { - state = state.copyWith(isLoading: true, errorEvent: null); - await _load(); - } - - Future uploadPhoto() async { - if (state.isSaving) return; - - final picker = ImagePicker(); - final image = await picker.pickImage( - source: ImageSource.gallery, - maxWidth: 800, - imageQuality: 80, - ); - if (image == null) return; - - final device = ref.read(selectedDeviceProvider).value; - if (device == null) return; - - state = state.copyWith( - isSaving: true, - errorEvent: null, - successEvent: null, - ); - - try { - final photoId = await _repository.uploadImage(path: image.path); - if (!ref.mounted) return; - - await _repository.setBackgroundImage( - deviceId: device.id, - photoId: photoId, - ); - if (!ref.mounted) return; - - await _refreshDevice(device); - if (!ref.mounted) return; - - unawaited(_tracking.legacyDeviceBackgroundImageUploaded()); - - state = state.copyWith( - isSaving: false, - successEvent: BackgroundImageSuccessEvent.uploaded, - ); - - await reload(); - } catch (e) { - if (!ref.mounted) return; - state = state.copyWith( - isSaving: false, - errorEvent: BackgroundImageErrorEvent.upload, - ); - } - } - - Future setAsBackground(String photoId) async { - final device = ref.read(selectedDeviceProvider).value; - if (device == null) return; - - state = state.copyWith( - isSaving: true, - errorEvent: null, - successEvent: null, - ); - - try { - await _repository.setBackgroundImage( - deviceId: device.id, - photoId: photoId, - ); - if (!ref.mounted) return; - - await _refreshDevice(device); - if (!ref.mounted) return; - - unawaited(_tracking.legacyDeviceBackgroundImageChanged()); - - state = state.copyWith( - isSaving: false, - successEvent: BackgroundImageSuccessEvent.backgroundSet, - ); - - await reload(); - } catch (e) { - if (!ref.mounted) return; - state = state.copyWith( - isSaving: false, - errorEvent: BackgroundImageErrorEvent.set, - ); - } - } - - Future _refreshDevice(DeviceEntity currentDevice) async { - final devices = await _devicesRepository.getDevices(); - if (!ref.mounted) return; - - final updated = devices.firstWhere( - (d) => d.identificator == currentDevice.identificator, - orElse: () => currentDevice, - ); - ref.read(selectedDeviceProvider.notifier).setSelectedDevice(updated); - } -} diff --git a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_state.dart b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_state.dart deleted file mode 100644 index c94f2830..00000000 --- a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_state.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'background_image_view_state.freezed.dart'; - -enum BackgroundImageErrorEvent { load, upload, set } - -enum BackgroundImageSuccessEvent { uploaded, backgroundSet } - -@freezed -abstract class BackgroundImageViewState with _$BackgroundImageViewState { - const factory BackgroundImageViewState({ - @Default([]) List photos, - @Default(true) bool isLoading, - @Default(false) bool isSaving, - String? currentBackgroundId, - BackgroundImageSuccessEvent? successEvent, - BackgroundImageErrorEvent? errorEvent, - }) = _BackgroundImageViewState; -} diff --git a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_state.freezed.dart b/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_state.freezed.dart deleted file mode 100644 index c9e86d30..00000000 --- a/modules/legacy/modules/device_management/lib/src/features/background_image/presentation/state/background_image_view_state.freezed.dart +++ /dev/null @@ -1,292 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'background_image_view_state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$BackgroundImageViewState { - - List get photos; bool get isLoading; bool get isSaving; String? get currentBackgroundId; BackgroundImageSuccessEvent? get successEvent; BackgroundImageErrorEvent? get errorEvent; -/// Create a copy of BackgroundImageViewState -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$BackgroundImageViewStateCopyWith get copyWith => _$BackgroundImageViewStateCopyWithImpl(this as BackgroundImageViewState, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is BackgroundImageViewState&&const DeepCollectionEquality().equals(other.photos, photos)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.currentBackgroundId, currentBackgroundId) || other.currentBackgroundId == currentBackgroundId)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)); -} - - -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(photos),isLoading,isSaving,currentBackgroundId,successEvent,errorEvent); - -@override -String toString() { - return 'BackgroundImageViewState(photos: $photos, isLoading: $isLoading, isSaving: $isSaving, currentBackgroundId: $currentBackgroundId, successEvent: $successEvent, errorEvent: $errorEvent)'; -} - - -} - -/// @nodoc -abstract mixin class $BackgroundImageViewStateCopyWith<$Res> { - factory $BackgroundImageViewStateCopyWith(BackgroundImageViewState value, $Res Function(BackgroundImageViewState) _then) = _$BackgroundImageViewStateCopyWithImpl; -@useResult -$Res call({ - List photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent -}); - - - - -} -/// @nodoc -class _$BackgroundImageViewStateCopyWithImpl<$Res> - implements $BackgroundImageViewStateCopyWith<$Res> { - _$BackgroundImageViewStateCopyWithImpl(this._self, this._then); - - final BackgroundImageViewState _self; - final $Res Function(BackgroundImageViewState) _then; - -/// Create a copy of BackgroundImageViewState -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? photos = null,Object? isLoading = null,Object? isSaving = null,Object? currentBackgroundId = freezed,Object? successEvent = freezed,Object? errorEvent = freezed,}) { - return _then(_self.copyWith( -photos: null == photos ? _self.photos : photos // ignore: cast_nullable_to_non_nullable -as List,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable -as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable -as bool,currentBackgroundId: freezed == currentBackgroundId ? _self.currentBackgroundId : currentBackgroundId // ignore: cast_nullable_to_non_nullable -as String?,successEvent: freezed == successEvent ? _self.successEvent : successEvent // ignore: cast_nullable_to_non_nullable -as BackgroundImageSuccessEvent?,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable -as BackgroundImageErrorEvent?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [BackgroundImageViewState]. -extension BackgroundImageViewStatePatterns on BackgroundImageViewState { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _BackgroundImageViewState value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _BackgroundImageViewState() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _BackgroundImageViewState value) $default,){ -final _that = this; -switch (_that) { -case _BackgroundImageViewState(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _BackgroundImageViewState value)? $default,){ -final _that = this; -switch (_that) { -case _BackgroundImageViewState() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( List photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _BackgroundImageViewState() when $default != null: -return $default(_that.photos,_that.isLoading,_that.isSaving,_that.currentBackgroundId,_that.successEvent,_that.errorEvent);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( List photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent) $default,) {final _that = this; -switch (_that) { -case _BackgroundImageViewState(): -return $default(_that.photos,_that.isLoading,_that.isSaving,_that.currentBackgroundId,_that.successEvent,_that.errorEvent);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( List photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent)? $default,) {final _that = this; -switch (_that) { -case _BackgroundImageViewState() when $default != null: -return $default(_that.photos,_that.isLoading,_that.isSaving,_that.currentBackgroundId,_that.successEvent,_that.errorEvent);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _BackgroundImageViewState implements BackgroundImageViewState { - const _BackgroundImageViewState({final List photos = const [], this.isLoading = true, this.isSaving = false, this.currentBackgroundId, this.successEvent, this.errorEvent}): _photos = photos; - - - final List _photos; -@override@JsonKey() List get photos { - if (_photos is EqualUnmodifiableListView) return _photos; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_photos); -} - -@override@JsonKey() final bool isLoading; -@override@JsonKey() final bool isSaving; -@override final String? currentBackgroundId; -@override final BackgroundImageSuccessEvent? successEvent; -@override final BackgroundImageErrorEvent? errorEvent; - -/// Create a copy of BackgroundImageViewState -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$BackgroundImageViewStateCopyWith<_BackgroundImageViewState> get copyWith => __$BackgroundImageViewStateCopyWithImpl<_BackgroundImageViewState>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _BackgroundImageViewState&&const DeepCollectionEquality().equals(other._photos, _photos)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.currentBackgroundId, currentBackgroundId) || other.currentBackgroundId == currentBackgroundId)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)); -} - - -@override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_photos),isLoading,isSaving,currentBackgroundId,successEvent,errorEvent); - -@override -String toString() { - return 'BackgroundImageViewState(photos: $photos, isLoading: $isLoading, isSaving: $isSaving, currentBackgroundId: $currentBackgroundId, successEvent: $successEvent, errorEvent: $errorEvent)'; -} - - -} - -/// @nodoc -abstract mixin class _$BackgroundImageViewStateCopyWith<$Res> implements $BackgroundImageViewStateCopyWith<$Res> { - factory _$BackgroundImageViewStateCopyWith(_BackgroundImageViewState value, $Res Function(_BackgroundImageViewState) _then) = __$BackgroundImageViewStateCopyWithImpl; -@override @useResult -$Res call({ - List photos, bool isLoading, bool isSaving, String? currentBackgroundId, BackgroundImageSuccessEvent? successEvent, BackgroundImageErrorEvent? errorEvent -}); - - - - -} -/// @nodoc -class __$BackgroundImageViewStateCopyWithImpl<$Res> - implements _$BackgroundImageViewStateCopyWith<$Res> { - __$BackgroundImageViewStateCopyWithImpl(this._self, this._then); - - final _BackgroundImageViewState _self; - final $Res Function(_BackgroundImageViewState) _then; - -/// Create a copy of BackgroundImageViewState -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? photos = null,Object? isLoading = null,Object? isSaving = null,Object? currentBackgroundId = freezed,Object? successEvent = freezed,Object? errorEvent = freezed,}) { - return _then(_BackgroundImageViewState( -photos: null == photos ? _self._photos : photos // ignore: cast_nullable_to_non_nullable -as List,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable -as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable -as bool,currentBackgroundId: freezed == currentBackgroundId ? _self.currentBackgroundId : currentBackgroundId // ignore: cast_nullable_to_non_nullable -as String?,successEvent: freezed == successEvent ? _self.successEvent : successEvent // ignore: cast_nullable_to_non_nullable -as BackgroundImageSuccessEvent?,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable -as BackgroundImageErrorEvent?, - )); -} - - -} - -// dart format on diff --git a/modules/legacy/modules/device_management/test/features/background_image/background_image_controller_test.dart b/modules/legacy/modules/device_management/test/features/background_image/background_image_controller_test.dart new file mode 100644 index 00000000..17a51dce --- /dev/null +++ b/modules/legacy/modules/device_management/test/features/background_image/background_image_controller_test.dart @@ -0,0 +1,103 @@ +import 'package:device_management/src/core/domain/repositories/background_image_repository.dart'; +import 'package:device_management/src/core/providers/background_image_repository_provider.dart'; +import 'package:device_management/src/features/background_image/presentation/providers/background_image_controller.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:sf_infrastructure/sf_infrastructure.dart'; +import 'package:sf_shared/sf_shared.dart'; +import 'package:sf_shared/testing.dart'; +import 'package:sf_tracking/sf_tracking.dart'; + +class MockBackgroundImageRepository extends Mock + implements BackgroundImageRepository {} + +class MockSharedDevicesRepository extends Mock + implements SharedDevicesRepository {} + +const _device = DeviceEntity( + id: 'device-1', + identificator: 'imei-1', + carrierName: 'Watch', +); + +void main() { + setUpAll(() { + registerFallbackValue(_device); + }); + + ProviderContainer buildContainer({ + required BackgroundImageRepository repo, + required SharedDevicesRepository devicesRepo, + }) { + return makeContainer( + overrides: [ + backgroundImageRepositoryProvider.overrideWithValue(repo), + sharedDevicesRepositoryProvider.overrideWithValue(devicesRepo), + sfTrackingProvider.overrideWithValue( + SfTrackingRepository(clients: const []), + ), + ], + ); + } + + group('BackgroundImageController.setAsBackground', () { + test('updates backend and refreshes device on success', () async { + final repo = MockBackgroundImageRepository(); + final devicesRepo = MockSharedDevicesRepository(); + when( + () => repo.setBackgroundImage( + deviceId: any(named: 'deviceId'), + photoId: any(named: 'photoId'), + ), + ).thenAnswer((_) async {}); + when(() => devicesRepo.getDevices()).thenAnswer((_) async => const []); + + final container = + buildContainer(repo: repo, devicesRepo: devicesRepo); + addTearDown(container.dispose); + + await container + .read(backgroundImageControllerProvider.notifier) + .setAsBackground(device: _device, photoId: 'photo-1'); + + expect( + container.read(backgroundImageControllerProvider), + isA>(), + ); + expect( + container + .read(backgroundImageControllerProvider.notifier) + .lastAction, + BackgroundImageAction.backgroundSet, + ); + verify( + () => repo.setBackgroundImage(deviceId: 'device-1', photoId: 'photo-1'), + ).called(1); + }); + + test('exposes AsyncError when repository fails', () async { + final repo = MockBackgroundImageRepository(); + final devicesRepo = MockSharedDevicesRepository(); + when( + () => repo.setBackgroundImage( + deviceId: any(named: 'deviceId'), + photoId: any(named: 'photoId'), + ), + ).thenThrow(const ApiException(message: 'boom', isNetworkError: true)); + + final container = + buildContainer(repo: repo, devicesRepo: devicesRepo); + addTearDown(container.dispose); + + await container + .read(backgroundImageControllerProvider.notifier) + .setAsBackground(device: _device, photoId: 'photo-1'); + + expect( + container.read(backgroundImageControllerProvider), + isA>(), + ); + }); + }); +}