feat: change background image

This commit is contained in:
2026-03-24 17:31:49 +01:00
parent 1ffeea8b77
commit cb70973d3b
14 changed files with 500 additions and 15 deletions

View File

@@ -26,7 +26,7 @@ late final GoRouter appRouter;
void configureAppRouter() { void configureAppRouter() {
appRouter = GoRouter( appRouter = GoRouter(
navigatorKey: rootNavigatorKey, navigatorKey: rootNavigatorKey,
initialLocation: AppRoutes.splash, initialLocation: AppRoutes.controlPanel,
debugLogDiagnostics: true, debugLogDiagnostics: true,
routes: [ routes: [
GoRoute( GoRoute(

View File

@@ -0,0 +1,17 @@
import '../models/get_background_image_response_model.dart';
abstract class BackgroundImageRemoteDatasource {
Future<GetBackgroundImageResponseModel> getBackgroundImage({
required String deviceId,
});
Future<void> uploadImage({
required String deviceId,
required String path
});
Future<void> setBackgroundImage({
required String deviceId,
required String fileId
});
}

View File

@@ -0,0 +1,61 @@
import 'package:dio/dio.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_infrastructure/configure_dependencies.dart';
import '../models/get_background_image_response_model.dart';
import 'background_image_remote_datasource.dart';
class BackgroundImageRemoteDatasourceImpl implements BackgroundImageRemoteDatasource {
BackgroundImageRemoteDatasourceImpl(this._repository);
final QuestiaRepository _repository;
@override
Future<GetBackgroundImageResponseModel> getBackgroundImage({required String deviceId}) async {
final response = await safeCall(
() => _repository.get<dynamic>(
'/devices/$deviceId/background-image',
),
'Error getting background image',
);
final data = response.data;
if (data == null || data.isEmpty) {
throw Exception('Empty response from /auth/totp/secret');
}
final model = GetBackgroundImageResponseModel.fromJson(data);
return model;
}
@override
Future<void> uploadImage({required String deviceId, required String path}) async {
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(path),
});
await safeCall(
() => _repository.post<dynamic>(
'/photos',
body: formData
),
'Error creating image',
);
}
@override
Future<void> setBackgroundImage({required String deviceId, required String fileId}) async {
await safeCall(
() => _repository.put<dynamic>(
'/devices/$deviceId/background-image',
body: {
'photoId': fileId
}
),
'Error creating image',
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'get_pictures_response_model.dart';
part 'get_background_image_response_model.freezed.dart';
part 'get_background_image_response_model.g.dart';
@freezed
abstract class GetBackgroundImageResponseModel with _$GetBackgroundImageResponseModel {
const factory GetBackgroundImageResponseModel({
required GetPicturesItemResponseModel item,
}) = _GetBackgroundImageResponseModel;
factory GetBackgroundImageResponseModel.fromJson(Map<String, dynamic> json) =>
_$GetBackgroundImageResponseModelFromJson(json);
}
extension GetBackgroundImageResponseModelMapper on GetBackgroundImageResponseModel {
PictureEntity toEntity() {
return PictureEntity(
id: item.id,
deviceIdentificator: item.deviceIdentificator,
imgType: item.imgType,
timestamp: item.timestamp,
fileId: item.fileId,
fileName: item.fileName,
contentType: item.contentType,
createdAt: item.createdAt,
);
}
}

View File

@@ -0,0 +1,295 @@
// 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 'get_background_image_response_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$GetBackgroundImageResponseModel {
GetPicturesItemResponseModel get item;
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$GetBackgroundImageResponseModelCopyWith<GetBackgroundImageResponseModel> get copyWith => _$GetBackgroundImageResponseModelCopyWithImpl<GetBackgroundImageResponseModel>(this as GetBackgroundImageResponseModel, _$identity);
/// Serializes this GetBackgroundImageResponseModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetBackgroundImageResponseModel&&(identical(other.item, item) || other.item == item));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,item);
@override
String toString() {
return 'GetBackgroundImageResponseModel(item: $item)';
}
}
/// @nodoc
abstract mixin class $GetBackgroundImageResponseModelCopyWith<$Res> {
factory $GetBackgroundImageResponseModelCopyWith(GetBackgroundImageResponseModel value, $Res Function(GetBackgroundImageResponseModel) _then) = _$GetBackgroundImageResponseModelCopyWithImpl;
@useResult
$Res call({
GetPicturesItemResponseModel item
});
$GetPicturesItemResponseModelCopyWith<$Res> get item;
}
/// @nodoc
class _$GetBackgroundImageResponseModelCopyWithImpl<$Res>
implements $GetBackgroundImageResponseModelCopyWith<$Res> {
_$GetBackgroundImageResponseModelCopyWithImpl(this._self, this._then);
final GetBackgroundImageResponseModel _self;
final $Res Function(GetBackgroundImageResponseModel) _then;
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? item = null,}) {
return _then(_self.copyWith(
item: null == item ? _self.item : item // ignore: cast_nullable_to_non_nullable
as GetPicturesItemResponseModel,
));
}
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$GetPicturesItemResponseModelCopyWith<$Res> get item {
return $GetPicturesItemResponseModelCopyWith<$Res>(_self.item, (value) {
return _then(_self.copyWith(item: value));
});
}
}
/// Adds pattern-matching-related methods to [GetBackgroundImageResponseModel].
extension GetBackgroundImageResponseModelPatterns on GetBackgroundImageResponseModel {
/// 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 extends Object?>(TResult Function( _GetBackgroundImageResponseModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel() 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 extends Object?>(TResult Function( _GetBackgroundImageResponseModel value) $default,){
final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel():
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 extends Object?>(TResult? Function( _GetBackgroundImageResponseModel value)? $default,){
final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel() 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 extends Object?>(TResult Function( GetPicturesItemResponseModel item)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel() when $default != null:
return $default(_that.item);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 extends Object?>(TResult Function( GetPicturesItemResponseModel item) $default,) {final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel():
return $default(_that.item);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 extends Object?>(TResult? Function( GetPicturesItemResponseModel item)? $default,) {final _that = this;
switch (_that) {
case _GetBackgroundImageResponseModel() when $default != null:
return $default(_that.item);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _GetBackgroundImageResponseModel implements GetBackgroundImageResponseModel {
const _GetBackgroundImageResponseModel({required this.item});
factory _GetBackgroundImageResponseModel.fromJson(Map<String, dynamic> json) => _$GetBackgroundImageResponseModelFromJson(json);
@override final GetPicturesItemResponseModel item;
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$GetBackgroundImageResponseModelCopyWith<_GetBackgroundImageResponseModel> get copyWith => __$GetBackgroundImageResponseModelCopyWithImpl<_GetBackgroundImageResponseModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$GetBackgroundImageResponseModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetBackgroundImageResponseModel&&(identical(other.item, item) || other.item == item));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,item);
@override
String toString() {
return 'GetBackgroundImageResponseModel(item: $item)';
}
}
/// @nodoc
abstract mixin class _$GetBackgroundImageResponseModelCopyWith<$Res> implements $GetBackgroundImageResponseModelCopyWith<$Res> {
factory _$GetBackgroundImageResponseModelCopyWith(_GetBackgroundImageResponseModel value, $Res Function(_GetBackgroundImageResponseModel) _then) = __$GetBackgroundImageResponseModelCopyWithImpl;
@override @useResult
$Res call({
GetPicturesItemResponseModel item
});
@override $GetPicturesItemResponseModelCopyWith<$Res> get item;
}
/// @nodoc
class __$GetBackgroundImageResponseModelCopyWithImpl<$Res>
implements _$GetBackgroundImageResponseModelCopyWith<$Res> {
__$GetBackgroundImageResponseModelCopyWithImpl(this._self, this._then);
final _GetBackgroundImageResponseModel _self;
final $Res Function(_GetBackgroundImageResponseModel) _then;
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? item = null,}) {
return _then(_GetBackgroundImageResponseModel(
item: null == item ? _self.item : item // ignore: cast_nullable_to_non_nullable
as GetPicturesItemResponseModel,
));
}
/// Create a copy of GetBackgroundImageResponseModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$GetPicturesItemResponseModelCopyWith<$Res> get item {
return $GetPicturesItemResponseModelCopyWith<$Res>(_self.item, (value) {
return _then(_self.copyWith(item: value));
});
}
}
// dart format on

View File

@@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'get_background_image_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_GetBackgroundImageResponseModel _$GetBackgroundImageResponseModelFromJson(
Map<String, dynamic> json,
) => _GetBackgroundImageResponseModel(
item: GetPicturesItemResponseModel.fromJson(
json['item'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$GetBackgroundImageResponseModelToJson(
_GetBackgroundImageResponseModel instance,
) => <String, dynamic>{'item': instance.item};

View File

@@ -0,0 +1,28 @@
import 'package:device_management/src/core/data/models/get_background_image_response_model.dart';
import '../../../features/remote_connection/domain/entities/picture_entity.dart';
import '../../domain/repositories/background_image_repository.dart';
import '../datasources/background_image_remote_datasource.dart';
class BackgroundImageRepositoryImpl implements BackgroundImageRepository {
const BackgroundImageRepositoryImpl(this._remote);
final BackgroundImageRemoteDatasource _remote;
@override
Future<PictureEntity> getBackgroundImage({required String deviceId}) async {
final model = await _remote.getBackgroundImage(deviceId: deviceId);
return model.toEntity();
}
@override
Future<void> uploadImage({required String deviceId, required String path}) {
return _remote.uploadImage(deviceId: deviceId, path: path);
}
@override
Future<void> setBackgroundImage({required String deviceId, required String fileId}) {
return _remote.setBackgroundImage(deviceId: deviceId, fileId: fileId);
}
}

View File

@@ -0,0 +1,8 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
abstract class BackgroundImageRepository {
Future<PictureEntity> getBackgroundImage({required String deviceId});
Future<void> uploadImage({required String deviceId, required String path});
Future<void> setBackgroundImage({required String deviceId, required String fileId});
}

View File

@@ -0,0 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import '../data/datasources/background_image_remote_datasource.dart';
import '../data/datasources/background_image_remote_datasource_impl.dart';
final backgroundImageRemoteDatasourceProvider =
Provider<BackgroundImageRemoteDatasource>((ref) {
final questiaRepository = getIt<QuestiaRepository>();
return BackgroundImageRemoteDatasourceImpl(questiaRepository);
});

View File

@@ -0,0 +1,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../data/repositories/background_image_repository_impl.dart';
import '../domain/repositories/background_image_repository.dart';
import 'background_image_remote_datasource_provider.dart';
final backgroundImageRepositoryProvider =
Provider<BackgroundImageRepository>((ref) {
final remote = ref.read(backgroundImageRemoteDatasourceProvider);
return BackgroundImageRepositoryImpl(remote);
});

View File

@@ -121,8 +121,8 @@ class BackgroundImageScreen extends ConsumerWidget {
const Icon(Icons.add_photo_alternate_outlined, size: 140, color: Colors.grey), const Icon(Icons.add_photo_alternate_outlined, size: 140, color: Colors.grey),
const SizedBox(height: 28), const SizedBox(height: 28),
state.isSaving state.isSaving
? const Text('Pulsa para seleccionar una foto') ? const CircularProgressIndicator()
: const CircularProgressIndicator() : const Text('Pulsa para seleccionar una foto')
], ],
); );
}, },

View File

@@ -3,6 +3,8 @@ import 'package:image_picker/image_picker.dart';
import 'package:legacy_shared/legacy_shared.dart'; import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_localizations/sf_localizations.dart'; import 'package:sf_localizations/sf_localizations.dart';
import '../../../../core/domain/repositories/background_image_repository.dart';
import '../../../../core/providers/background_image_repository_provider.dart';
import 'background_image_view_state.dart'; import 'background_image_view_state.dart';
final backgroundImageViewModelProvider = final backgroundImageViewModelProvider =
@@ -11,11 +13,11 @@ NotifierProvider.autoDispose<BackgroundImageViewModel, BackgroundImageViewState>
); );
class BackgroundImageViewModel extends Notifier<BackgroundImageViewState> { class BackgroundImageViewModel extends Notifier<BackgroundImageViewState> {
late final CommandsRepository _commandsRepository; late final BackgroundImageRepository _repository;
@override @override
BackgroundImageViewState build() { BackgroundImageViewState build() {
_commandsRepository = ref.read(commandsRepositoryProvider); _repository = ref.read(backgroundImageRepositoryProvider);
Future.microtask(_load); Future.microtask(_load);
return const BackgroundImageViewState(); return const BackgroundImageViewState();
} }
@@ -25,8 +27,9 @@ class BackgroundImageViewModel extends Notifier<BackgroundImageViewState> {
final device = ref.read(selectedDeviceProvider); final device = ref.read(selectedDeviceProvider);
if (device == null) return; if (device == null) return;
// final image = await _repository.getBackgroundImage(deviceId: device.id); final image = await _repository.getBackgroundImage(deviceId: device.identificator);
state = state.copyWith(image: '', isLoading: false);
state = state.copyWith(image: image.fileId, isLoading: false);
} catch (e) { } catch (e) {
state = state.copyWith( state = state.copyWith(
isLoading: false, isLoading: false,
@@ -53,13 +56,12 @@ class BackgroundImageViewModel extends Notifier<BackgroundImageViewState> {
if (device == null) return; if (device == null) return;
try { try {
final request = SendCommandRequestModel(
device: device.identificator,
command: DeviceCommand.setBackgroundImage,
data: {},
);
await _commandsRepository.send(request: request); await _repository.uploadImage(deviceId: device.identificator, path: image.path);
final fileId = '';
await _repository.setBackgroundImage(deviceId: device.identificator, fileId: fileId);
state = state.copyWith( state = state.copyWith(
image: image.path, image: image.path,
@@ -67,6 +69,7 @@ class BackgroundImageViewModel extends Notifier<BackgroundImageViewState> {
successMessage: I18n.alarmUpdated, successMessage: I18n.alarmUpdated,
); );
} catch (e) { } catch (e) {
print(e);
state = state.copyWith( state = state.copyWith(
isSaving: false, isSaving: false,
errorMessage: formatErrorMessage(e), errorMessage: formatErrorMessage(e),

View File

@@ -7,7 +7,7 @@ abstract class BackgroundImageViewState with _$BackgroundImageViewState {
const factory BackgroundImageViewState({ const factory BackgroundImageViewState({
@Default('') String image, @Default('') String image,
@Default(true) bool isLoading, @Default(true) bool isLoading,
@Default(true) bool isSaving, @Default(false) bool isSaving,
@Default('') String successMessage, @Default('') String successMessage,
@Default('') String errorMessage, @Default('') String errorMessage,
}) = _BackgroundImageViewState; }) = _BackgroundImageViewState;

View File

@@ -210,7 +210,7 @@ return $default(_that.image,_that.isLoading,_that.isSaving,_that.successMessage,
class _BackgroundImageViewState implements BackgroundImageViewState { class _BackgroundImageViewState implements BackgroundImageViewState {
const _BackgroundImageViewState({this.image = '', this.isLoading = false, this.isSaving = true, this.successMessage = '', this.errorMessage = ''}); const _BackgroundImageViewState({this.image = '', this.isLoading = true, this.isSaving = false, this.successMessage = '', this.errorMessage = ''});
@override@JsonKey() final String image; @override@JsonKey() final String image;