diff --git a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/locate_device_screen.dart b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/locate_device_screen.dart index 8f856fac..3b86a688 100644 --- a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/locate_device_screen.dart +++ b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/locate_device_screen.dart @@ -1,40 +1,26 @@ import 'package:design_system/design_system.dart'; -import 'package:legacy_theme/legacy_theme.dart'; +import 'package:device_management/src/features/locate_device/presentation/providers/locate_device_controller.dart'; +import 'package:device_management/src/features/locate_device/presentation/widgets/locate_device_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:legacy_theme/legacy_theme.dart'; import 'package:legacy_ui/legacy_ui.dart'; import 'package:sf_localizations/sf_localizations.dart'; +import 'package:sf_shared/sf_shared.dart'; import 'package:utils/utils.dart'; -import 'state/locate_device_view_model.dart'; -import 'widgets/locate_device_dialog.dart'; - class LocateDeviceScreen extends ConsumerWidget { const LocateDeviceScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - - ref.listen(locateDeviceViewModelProvider.select((s) => s.errorMessage), ( - previous, - next, - ) { - if (next.isNotEmpty) { - showTopSnackbar(context, message: next, type: MessageType.error); - } - }); - - ref.listen(locateDeviceViewModelProvider.select((s) => s.isComplete), ( - previous, - next, - ) { - if (next) { - showTopSnackbar( - context, - message: context.translate(I18n.sentSuccessfully), - type: MessageType.success, - ); + ref.listen(locateDeviceControllerProvider, (prev, next) async { + if (prev == null || !prev.isLoading || next.isLoading) return; + if (next.hasError) { + await showErrorDialog(context, I18n.deviceNotConnected); + return; } + await showSuccessDialog(context, I18n.sentSuccessfully); }); return LegacyPageLayout( @@ -70,7 +56,7 @@ class LocateDeviceScreen extends ConsumerWidget { ), child: PrimaryButton( onPressed: () { - showDialog( + showDialog( context: context, builder: (_) => const Dialog(child: LocateDeviceDialog()), ); diff --git a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/providers/locate_device_controller.dart b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/providers/locate_device_controller.dart new file mode 100644 index 00000000..924633c6 --- /dev/null +++ b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/providers/locate_device_controller.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:legacy_device_state/legacy_device_state.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:sf_tracking/sf_tracking.dart'; + +part 'locate_device_controller.g.dart'; + +@riverpod +class LocateDeviceController extends _$LocateDeviceController { + @override + FutureOr build() {} + + Future locateDevice({required String deviceIdentificator}) async { + unawaited(ref.read(sfTrackingProvider).legacyDeviceLocateRequested()); + state = const AsyncLoading(); + state = await AsyncValue.guard(() async { + await ref.read(commandsRepositoryProvider).send( + request: SendCommandRequestModel( + device: deviceIdentificator, + command: DeviceCommand.findDevice, + ), + ); + unawaited(ref.read(sfTrackingProvider).legacyDeviceLocateSuccess()); + }); + if (state.hasError) { + unawaited( + ref + .read(sfTrackingProvider) + .legacyDeviceLocateFailure(state.error.toString()), + ); + } + } +} diff --git a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/providers/locate_device_controller.g.dart b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/providers/locate_device_controller.g.dart new file mode 100644 index 00000000..e30f71b1 --- /dev/null +++ b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/providers/locate_device_controller.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'locate_device_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(LocateDeviceController) +const locateDeviceControllerProvider = LocateDeviceControllerProvider._(); + +final class LocateDeviceControllerProvider + extends $AsyncNotifierProvider { + const LocateDeviceControllerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'locateDeviceControllerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$locateDeviceControllerHash(); + + @$internal + @override + LocateDeviceController create() => LocateDeviceController(); +} + +String _$locateDeviceControllerHash() => + r'52baaa3b08385739e7dfbe8d363d46264ea1582b'; + +abstract class _$LocateDeviceController 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/locate_device/presentation/state/locate_device_view_model.dart b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/state/locate_device_view_model.dart deleted file mode 100644 index 73c1e9c6..00000000 --- a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/state/locate_device_view_model.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:legacy_device_state/legacy_device_state.dart'; -import 'package:sf_shared/sf_shared.dart'; -import 'package:sf_tracking/sf_tracking.dart'; - -import 'locate_device_view_state.dart'; - -final locateDeviceViewModelProvider = - NotifierProvider.autoDispose( - LocateDeviceViewModel.new, - ); - -class LocateDeviceViewModel extends Notifier { - late final CommandsRepository _commandsRepository; - late final SfTrackingRepository _tracking; - - @override - LocateDeviceViewState build() { - _commandsRepository = ref.read(commandsRepositoryProvider); - _tracking = ref.read(sfTrackingProvider); - - final device = ref.read(selectedDeviceProvider).value; - - return LocateDeviceViewState(device: device); - } - - Future locateDevice() async { - if (state.isLoading) return; - - unawaited(_tracking.legacyDeviceLocateRequested()); - - try { - state = state.copyWith( - isLoading: true, - isComplete: false, - errorMessage: '', - ); - - final request = SendCommandRequestModel( - device: state.device!.identificator, - command: DeviceCommand.findDevice, - ); - await _commandsRepository.send(request: request); - - unawaited(_tracking.legacyDeviceLocateSuccess()); - - state = state.copyWith(isLoading: false, isComplete: true); - } catch (e) { - unawaited(_tracking.legacyDeviceLocateFailure(e.toString())); - - state = state.copyWith( - isLoading: false, - isComplete: false, - errorMessage: e.toString(), - ); - } - } - - void reset() { - state = state.copyWith( - isLoading: false, - isComplete: false, - errorMessage: '', - ); - } -} diff --git a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/state/locate_device_view_state.dart b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/state/locate_device_view_state.dart deleted file mode 100644 index d9542b00..00000000 --- a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/state/locate_device_view_state.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:sf_shared/sf_shared.dart'; - -part 'locate_device_view_state.freezed.dart'; - -@freezed -abstract class LocateDeviceViewState with _$LocateDeviceViewState { - const factory LocateDeviceViewState({ - DeviceEntity? device, - @Default(false) bool isLoading, - @Default(false) bool isComplete, - @Default('') String errorMessage, - }) = _LocateDeviceViewState; -} diff --git a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/state/locate_device_view_state.freezed.dart b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/state/locate_device_view_state.freezed.dart deleted file mode 100644 index eea354ad..00000000 --- a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/state/locate_device_view_state.freezed.dart +++ /dev/null @@ -1,304 +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 'locate_device_view_state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$LocateDeviceViewState { - - DeviceEntity? get device; bool get isLoading; bool get isComplete; String get errorMessage; -/// Create a copy of LocateDeviceViewState -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$LocateDeviceViewStateCopyWith get copyWith => _$LocateDeviceViewStateCopyWithImpl(this as LocateDeviceViewState, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is LocateDeviceViewState&&(identical(other.device, device) || other.device == device)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); -} - - -@override -int get hashCode => Object.hash(runtimeType,device,isLoading,isComplete,errorMessage); - -@override -String toString() { - return 'LocateDeviceViewState(device: $device, isLoading: $isLoading, isComplete: $isComplete, errorMessage: $errorMessage)'; -} - - -} - -/// @nodoc -abstract mixin class $LocateDeviceViewStateCopyWith<$Res> { - factory $LocateDeviceViewStateCopyWith(LocateDeviceViewState value, $Res Function(LocateDeviceViewState) _then) = _$LocateDeviceViewStateCopyWithImpl; -@useResult -$Res call({ - DeviceEntity? device, bool isLoading, bool isComplete, String errorMessage -}); - - -$DeviceEntityCopyWith<$Res>? get device; - -} -/// @nodoc -class _$LocateDeviceViewStateCopyWithImpl<$Res> - implements $LocateDeviceViewStateCopyWith<$Res> { - _$LocateDeviceViewStateCopyWithImpl(this._self, this._then); - - final LocateDeviceViewState _self; - final $Res Function(LocateDeviceViewState) _then; - -/// Create a copy of LocateDeviceViewState -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? device = freezed,Object? isLoading = null,Object? isComplete = null,Object? errorMessage = null,}) { - return _then(_self.copyWith( -device: freezed == device ? _self.device : device // ignore: cast_nullable_to_non_nullable -as DeviceEntity?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable -as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable -as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable -as String, - )); -} -/// Create a copy of LocateDeviceViewState -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$DeviceEntityCopyWith<$Res>? get device { - if (_self.device == null) { - return null; - } - - return $DeviceEntityCopyWith<$Res>(_self.device!, (value) { - return _then(_self.copyWith(device: value)); - }); -} -} - - -/// Adds pattern-matching-related methods to [LocateDeviceViewState]. -extension LocateDeviceViewStatePatterns on LocateDeviceViewState { -/// 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( _LocateDeviceViewState value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _LocateDeviceViewState() 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( _LocateDeviceViewState value) $default,){ -final _that = this; -switch (_that) { -case _LocateDeviceViewState(): -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( _LocateDeviceViewState value)? $default,){ -final _that = this; -switch (_that) { -case _LocateDeviceViewState() 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( DeviceEntity? device, bool isLoading, bool isComplete, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _LocateDeviceViewState() when $default != null: -return $default(_that.device,_that.isLoading,_that.isComplete,_that.errorMessage);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( DeviceEntity? device, bool isLoading, bool isComplete, String errorMessage) $default,) {final _that = this; -switch (_that) { -case _LocateDeviceViewState(): -return $default(_that.device,_that.isLoading,_that.isComplete,_that.errorMessage);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( DeviceEntity? device, bool isLoading, bool isComplete, String errorMessage)? $default,) {final _that = this; -switch (_that) { -case _LocateDeviceViewState() when $default != null: -return $default(_that.device,_that.isLoading,_that.isComplete,_that.errorMessage);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _LocateDeviceViewState implements LocateDeviceViewState { - const _LocateDeviceViewState({this.device, this.isLoading = false, this.isComplete = false, this.errorMessage = ''}); - - -@override final DeviceEntity? device; -@override@JsonKey() final bool isLoading; -@override@JsonKey() final bool isComplete; -@override@JsonKey() final String errorMessage; - -/// Create a copy of LocateDeviceViewState -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$LocateDeviceViewStateCopyWith<_LocateDeviceViewState> get copyWith => __$LocateDeviceViewStateCopyWithImpl<_LocateDeviceViewState>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _LocateDeviceViewState&&(identical(other.device, device) || other.device == device)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)); -} - - -@override -int get hashCode => Object.hash(runtimeType,device,isLoading,isComplete,errorMessage); - -@override -String toString() { - return 'LocateDeviceViewState(device: $device, isLoading: $isLoading, isComplete: $isComplete, errorMessage: $errorMessage)'; -} - - -} - -/// @nodoc -abstract mixin class _$LocateDeviceViewStateCopyWith<$Res> implements $LocateDeviceViewStateCopyWith<$Res> { - factory _$LocateDeviceViewStateCopyWith(_LocateDeviceViewState value, $Res Function(_LocateDeviceViewState) _then) = __$LocateDeviceViewStateCopyWithImpl; -@override @useResult -$Res call({ - DeviceEntity? device, bool isLoading, bool isComplete, String errorMessage -}); - - -@override $DeviceEntityCopyWith<$Res>? get device; - -} -/// @nodoc -class __$LocateDeviceViewStateCopyWithImpl<$Res> - implements _$LocateDeviceViewStateCopyWith<$Res> { - __$LocateDeviceViewStateCopyWithImpl(this._self, this._then); - - final _LocateDeviceViewState _self; - final $Res Function(_LocateDeviceViewState) _then; - -/// Create a copy of LocateDeviceViewState -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? device = freezed,Object? isLoading = null,Object? isComplete = null,Object? errorMessage = null,}) { - return _then(_LocateDeviceViewState( -device: freezed == device ? _self.device : device // ignore: cast_nullable_to_non_nullable -as DeviceEntity?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable -as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable -as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable -as String, - )); -} - -/// Create a copy of LocateDeviceViewState -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$DeviceEntityCopyWith<$Res>? get device { - if (_self.device == null) { - return null; - } - - return $DeviceEntityCopyWith<$Res>(_self.device!, (value) { - return _then(_self.copyWith(device: value)); - }); -} -} - -// dart format on diff --git a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/widgets/locate_device_dialog.dart b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/widgets/locate_device_dialog.dart index 0b2b9d85..c389b437 100644 --- a/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/widgets/locate_device_dialog.dart +++ b/modules/legacy/modules/device_management/lib/src/features/locate_device/presentation/widgets/locate_device_dialog.dart @@ -1,35 +1,19 @@ import 'package:design_system/design_system.dart'; +import 'package:device_management/src/features/locate_device/presentation/providers/locate_device_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:sf_localizations/sf_localizations.dart'; -import 'package:utils/utils.dart'; - import 'package:legacy_device_state/legacy_device_state.dart'; - -import '../state/locate_device_view_model.dart'; import 'package:legacy_theme/legacy_theme.dart'; +import 'package:sf_localizations/sf_localizations.dart'; +import 'package:sf_shared/sf_shared.dart'; +import 'package:utils/utils.dart'; class LocateDeviceDialog extends ConsumerWidget { const LocateDeviceDialog({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final state = ref.watch(locateDeviceViewModelProvider); - final vm = ref.read(locateDeviceViewModelProvider.notifier); - - ref.listen(locateDeviceViewModelProvider.select((s) => s.isComplete), ( - previous, - next, - ) { - if (next && !(previous ?? false)) { - Future.delayed(const Duration(seconds: 1), () { - if (context.mounted) { - Navigator.pop(context); - vm.reset(); - } - }); - } - }); + final primaryColor = context.sfColors.legacyPrimary; return Container( padding: EdgeInsets.symmetric( @@ -40,95 +24,51 @@ class LocateDeviceDialog extends ConsumerWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (state.isLoading) - _DialogMessage(text: context.translate(I18n.sending)), - if (!state.isLoading && state.isComplete) - _DialogMessage( - text: context.translate(I18n.sentSuccessfully), + Text( + context.translate(I18n.locateDeviceConfirmMessage), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: SizeUtils.getByScreen(small: 19, big: 18), ), - if (state.errorMessage.isNotEmpty) ...[ - _DialogMessage( - text: context.translate(I18n.deviceNotConnected), - fontSize: SizeUtils.getByScreen(small: 20, big: 19), - ), - SizedBox(height: SizeUtils.getByScreen(small: 24, big: 23)), - PrimaryButton( - onPressed: () { - Navigator.pop(context); - vm.reset(); - }, - text: context.translate(I18n.ok), - color: context.sfColors.legacyPrimary, - height: SizeUtils.getByScreen(small: 38, big: 36), - radius: SizeUtils.getByScreen(small: 32, big: 34), - ), - ], - if (!state.isComplete && - !state.isLoading && - state.errorMessage.isEmpty) ...[ - Text( - context.translate(I18n.locateDeviceConfirmMessage), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: SizeUtils.getByScreen(small: 19, big: 18), + ), + SizedBox(height: SizeUtils.getByScreen(small: 24, big: 23)), + Row( + children: [ + Expanded( + child: PrimaryButton( + onPressed: () => Navigator.pop(context), + text: context.translate(I18n.cancel), + color: primaryColor, + height: SizeUtils.getByScreen(small: 38, big: 36), + radius: SizeUtils.getByScreen(small: 32, big: 34), + ), ), - ), - SizedBox(height: SizeUtils.getByScreen(small: 24, big: 23)), - Row( - children: [ - Expanded( - child: PrimaryButton( - onPressed: () => Navigator.pop(context), - text: context.translate(I18n.cancel), - color: context.sfColors.legacyPrimary, - height: SizeUtils.getByScreen(small: 38, big: 36), - radius: SizeUtils.getByScreen(small: 32, big: 34), - ), + SizedBox(width: SizeUtils.getByScreen(small: 8, big: 16)), + Expanded( + child: PrimaryButton( + onPressed: () async { + if (!await guardDeviceCommand(context, ref)) return; + final identificator = ref + .read(selectedDeviceProvider) + .value + ?.identificator; + if (identificator == null) return; + if (!context.mounted) return; + Navigator.pop(context); + ref + .read(locateDeviceControllerProvider.notifier) + .locateDevice(deviceIdentificator: identificator); + }, + text: context.translate(I18n.accept), + color: primaryColor, + height: SizeUtils.getByScreen(small: 38, big: 36), + radius: SizeUtils.getByScreen(small: 32, big: 34), ), - SizedBox(width: SizeUtils.getByScreen(small: 8, big: 16)), - Expanded( - child: PrimaryButton( - onPressed: () async { - if (!await guardDeviceCommand(context, ref)) return; - vm.locateDevice(); - }, - text: context.translate(I18n.accept), - color: context.sfColors.legacyPrimary, - height: SizeUtils.getByScreen(small: 38, big: 36), - radius: SizeUtils.getByScreen(small: 32, big: 34), - ), - ), - ], - ), - ], + ), + ], + ), ], ), ); } } - -class _DialogMessage extends StatelessWidget { - final String text; - final double? fontSize; - - const _DialogMessage({ - required this.text, - this.fontSize, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.symmetric( - vertical: SizeUtils.getByScreen(small: 16, big: 14), - ), - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: fontSize ?? SizeUtils.getByScreen(small: 25, big: 24), - ), - ), - ); - } -} diff --git a/modules/legacy/modules/device_management/lib/src/features/rewards/presentation/rewards_screen.dart b/modules/legacy/modules/device_management/lib/src/features/rewards/presentation/rewards_screen.dart index 19d81d81..d946e4e9 100644 --- a/modules/legacy/modules/device_management/lib/src/features/rewards/presentation/rewards_screen.dart +++ b/modules/legacy/modules/device_management/lib/src/features/rewards/presentation/rewards_screen.dart @@ -29,23 +29,28 @@ class RewardsScreen extends ConsumerWidget { return LegacyPageLayout( title: context.translate(I18n.rewards), - body: Column( - children: [ - Center( - child: Icon( - SFIcons.rewardsCircle, - size: SizeUtils.getByScreen(small: 180, big: 160), - color: primaryColor, + body: Padding( + padding: EdgeInsets.symmetric( + horizontal: SizeUtils.getByScreen(small: 24, big: 22), + ), + child: Column( + children: [ + Center( + child: Icon( + SFIcons.rewardsCircle, + size: SizeUtils.getByScreen(small: 180, big: 160), + color: primaryColor, + ), ), - ), - SizedBox(height: SizeUtils.getByScreen(small: 32, big: 28)), - Text( - context.translate(I18n.rewardsMessage), - textAlign: TextAlign.start, - ), - SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)), - const _CounterSection(), - ], + SizedBox(height: SizeUtils.getByScreen(small: 32, big: 28)), + Text( + context.translate(I18n.rewardsMessage), + textAlign: TextAlign.start, + ), + SizedBox(height: SizeUtils.getByScreen(small: 12, big: 10)), + const _CounterSection(), + ], + ), ), footer: Padding( padding: EdgeInsets.symmetric( diff --git a/modules/legacy/modules/device_management/test/features/locate_device/locate_device_controller_test.dart b/modules/legacy/modules/device_management/test/features/locate_device/locate_device_controller_test.dart new file mode 100644 index 00000000..89c631a5 --- /dev/null +++ b/modules/legacy/modules/device_management/test/features/locate_device/locate_device_controller_test.dart @@ -0,0 +1,75 @@ +import 'package:device_management/src/features/locate_device/presentation/providers/locate_device_controller.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:legacy_device_state/legacy_device_state.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:sf_infrastructure/sf_infrastructure.dart'; +import 'package:sf_shared/testing.dart'; +import 'package:sf_tracking/sf_tracking.dart'; + +class MockCommandsRepository extends Mock implements CommandsRepository {} + +void main() { + setUpAll(() { + registerFallbackValue( + SendCommandRequestModel( + device: 'x', + command: DeviceCommand.findDevice, + ), + ); + }); + + ProviderContainer buildContainer(CommandsRepository commands) { + return makeContainer( + overrides: [ + commandsRepositoryProvider.overrideWithValue(commands), + sfTrackingProvider.overrideWithValue( + SfTrackingRepository(clients: const []), + ), + ], + ); + } + + group('LocateDeviceController.locateDevice', () { + test('sends FIND_DEVICE command and transitions to AsyncData', () async { + final commands = MockCommandsRepository(); + when(() => commands.send(request: any(named: 'request'))) + .thenAnswer((_) async {}); + + final container = buildContainer(commands); + addTearDown(container.dispose); + + await container.read(locateDeviceControllerProvider.notifier).locateDevice( + deviceIdentificator: 'imei-1', + ); + + expect( + container.read(locateDeviceControllerProvider), + isA>(), + ); + final sent = verify( + () => commands.send(request: captureAny(named: 'request')), + ).captured.single as SendCommandRequestModel; + expect(sent.command, DeviceCommand.findDevice); + expect(sent.device, 'imei-1'); + }); + + test('exposes AsyncError when command fails', () async { + final commands = MockCommandsRepository(); + when(() => commands.send(request: any(named: 'request'))) + .thenThrow(const ApiException(message: 'boom', isNetworkError: true)); + + final container = buildContainer(commands); + addTearDown(container.dispose); + + await container.read(locateDeviceControllerProvider.notifier).locateDevice( + deviceIdentificator: 'imei-1', + ); + + expect( + container.read(locateDeviceControllerProvider), + isA>(), + ); + }); + }); +}