feat: merge remote-call feature and fix remote connection

- Implement photos API (GET /devices/identificator/{id}/photos)
  - Fix deviceId empty in commands (set before async load)
  - Fix missing await in call() method
  - Add ref.mounted checks on all async operations
  - Reload photos after REQUEST_PHOTO command
  - Add CountryPrefixPicker to spy call dialog
  - Add loading state and topSnackbar feedback on call
  - Handle empty photos list in gallery
  - Fix Expanded overflow in remote camera screen
  - Change keyboard to phone type in spy call
  - Remove unnecessary use cases
  - Add GetPicturesResponseModel with freezed
This commit is contained in:
2026-03-22 04:57:38 +01:00
39 changed files with 1111 additions and 451 deletions

View File

@@ -1,7 +0,0 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
abstract class FunctionsRemoteDatasource {
Future<List<PictureEntity>> getPictures({required String userId});
Future<PictureEntity> takePicture({required String userId});
}

View File

@@ -1,55 +0,0 @@
import 'package:device_management/src/core/data/datasources/functions_remote_datasource.dart';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
class FunctionsRemoteDatasourceImpl implements FunctionsRemoteDatasource {
FunctionsRemoteDatasourceImpl(this._repository);
final QuestiaRepository _repository;
@override
Future<List<PictureEntity>> getPictures({required String userId}) async {
/*try {
final response = await _repository.get<Map<String, dynamic>>(
'',
);
final data = response.data;
if (data == null || data.isEmpty) {
throw Exception('Empty response from /users/:userId/contacts');
}
final model = GetPicturesResponseModel.fromJson(data);
return model.toEntity();
} on DioException catch (error) {
throw mapDioError(error, defaultMessage: 'Error to get pictures');
}*/
return [];
}
@override
Future<PictureEntity> takePicture({required String userId}) async {
/*try {
final response = await _repository.get<Map<String, dynamic>>(
'',
);
final data = response.data;
if (data == null || data.isEmpty) {
throw Exception('Empty response from /users/:userId/contacts');
}
final model = GetContactsResponseModel.fromJson(data);
return model.toEntity();
} on DioException catch (error) {
throw mapDioError(error, defaultMessage: 'Error to get contacts');
}*/
return PictureEntity(
id: '1',
deviceId: '1111',
createdAt: DateTime.now(),
takenAt: DateTime.now(),
asset: 'assets/shared/images/iso_sf.png',
);
}
}

View File

@@ -0,0 +1,7 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
abstract class PicturesRemoteDatasource {
Future<List<PictureEntity>> getPictures({required String deviceId});
Future<PictureEntity> takePicture({required String deviceId});
}

View File

@@ -0,0 +1,37 @@
import 'package:device_management/src/core/data/datasources/pictures_remote_datasource.dart';
import 'package:device_management/src/core/data/models/get_pictures_response_model.dart';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:dio/dio.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
class PicturesRemoteDatasourceImpl implements PicturesRemoteDatasource {
PicturesRemoteDatasourceImpl(this._repository);
final QuestiaRepository _repository;
@override
Future<List<PictureEntity>> getPictures({required String deviceId}) async {
try {
final response = await _repository.get<Map<String, dynamic>>(
'/devices/identificator/$deviceId/photos',
);
final data = response.data;
if (data == null || data.isEmpty) {
return [];
}
final model = GetPicturesResponseModel.fromJson(data);
return model.toEntity();
} on DioException catch (error) {
if (error.response?.statusCode == 404) return [];
throw mapDioError(error, defaultMessage: 'Error getting pictures');
}
}
@override
Future<PictureEntity> takePicture({required String deviceId}) async {
throw UnimplementedError('takePicture is handled via commands');
}
}

View File

@@ -0,0 +1,50 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'get_pictures_response_model.freezed.dart';
part 'get_pictures_response_model.g.dart';
@freezed
abstract class GetPicturesResponseModel with _$GetPicturesResponseModel {
const factory GetPicturesResponseModel({
required List<GetPicturesItemResponseModel> items,
}) = _GetPicturesResponseModel;
factory GetPicturesResponseModel.fromJson(Map<String, dynamic> json) =>
_$GetPicturesResponseModelFromJson(json);
}
@freezed
abstract class GetPicturesItemResponseModel
with _$GetPicturesItemResponseModel {
const factory GetPicturesItemResponseModel({
required String id,
required String deviceIdentificator,
String? imgType,
String? timestamp,
required String fileId,
String? fileName,
String? contentType,
required int createdAt,
}) = _GetPicturesItemResponseModel;
factory GetPicturesItemResponseModel.fromJson(Map<String, dynamic> json) =>
_$GetPicturesItemResponseModelFromJson(json);
}
extension GetPicturesResponseModelMapper on GetPicturesResponseModel {
List<PictureEntity> toEntity() {
return items
.map((item) => 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,
))
.toList();
}
}

View File

@@ -0,0 +1,567 @@
// 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_pictures_response_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$GetPicturesResponseModel {
List<GetPicturesItemResponseModel> get items;
/// Create a copy of GetPicturesResponseModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$GetPicturesResponseModelCopyWith<GetPicturesResponseModel> get copyWith => _$GetPicturesResponseModelCopyWithImpl<GetPicturesResponseModel>(this as GetPicturesResponseModel, _$identity);
/// Serializes this GetPicturesResponseModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetPicturesResponseModel&&const DeepCollectionEquality().equals(other.items, items));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(items));
@override
String toString() {
return 'GetPicturesResponseModel(items: $items)';
}
}
/// @nodoc
abstract mixin class $GetPicturesResponseModelCopyWith<$Res> {
factory $GetPicturesResponseModelCopyWith(GetPicturesResponseModel value, $Res Function(GetPicturesResponseModel) _then) = _$GetPicturesResponseModelCopyWithImpl;
@useResult
$Res call({
List<GetPicturesItemResponseModel> items
});
}
/// @nodoc
class _$GetPicturesResponseModelCopyWithImpl<$Res>
implements $GetPicturesResponseModelCopyWith<$Res> {
_$GetPicturesResponseModelCopyWithImpl(this._self, this._then);
final GetPicturesResponseModel _self;
final $Res Function(GetPicturesResponseModel) _then;
/// Create a copy of GetPicturesResponseModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? items = null,}) {
return _then(_self.copyWith(
items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
as List<GetPicturesItemResponseModel>,
));
}
}
/// Adds pattern-matching-related methods to [GetPicturesResponseModel].
extension GetPicturesResponseModelPatterns on GetPicturesResponseModel {
/// 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( _GetPicturesResponseModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _GetPicturesResponseModel() 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( _GetPicturesResponseModel value) $default,){
final _that = this;
switch (_that) {
case _GetPicturesResponseModel():
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( _GetPicturesResponseModel value)? $default,){
final _that = this;
switch (_that) {
case _GetPicturesResponseModel() 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( List<GetPicturesItemResponseModel> items)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _GetPicturesResponseModel() when $default != null:
return $default(_that.items);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( List<GetPicturesItemResponseModel> items) $default,) {final _that = this;
switch (_that) {
case _GetPicturesResponseModel():
return $default(_that.items);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( List<GetPicturesItemResponseModel> items)? $default,) {final _that = this;
switch (_that) {
case _GetPicturesResponseModel() when $default != null:
return $default(_that.items);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _GetPicturesResponseModel implements GetPicturesResponseModel {
const _GetPicturesResponseModel({required final List<GetPicturesItemResponseModel> items}): _items = items;
factory _GetPicturesResponseModel.fromJson(Map<String, dynamic> json) => _$GetPicturesResponseModelFromJson(json);
final List<GetPicturesItemResponseModel> _items;
@override List<GetPicturesItemResponseModel> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
/// Create a copy of GetPicturesResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$GetPicturesResponseModelCopyWith<_GetPicturesResponseModel> get copyWith => __$GetPicturesResponseModelCopyWithImpl<_GetPicturesResponseModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$GetPicturesResponseModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetPicturesResponseModel&&const DeepCollectionEquality().equals(other._items, _items));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_items));
@override
String toString() {
return 'GetPicturesResponseModel(items: $items)';
}
}
/// @nodoc
abstract mixin class _$GetPicturesResponseModelCopyWith<$Res> implements $GetPicturesResponseModelCopyWith<$Res> {
factory _$GetPicturesResponseModelCopyWith(_GetPicturesResponseModel value, $Res Function(_GetPicturesResponseModel) _then) = __$GetPicturesResponseModelCopyWithImpl;
@override @useResult
$Res call({
List<GetPicturesItemResponseModel> items
});
}
/// @nodoc
class __$GetPicturesResponseModelCopyWithImpl<$Res>
implements _$GetPicturesResponseModelCopyWith<$Res> {
__$GetPicturesResponseModelCopyWithImpl(this._self, this._then);
final _GetPicturesResponseModel _self;
final $Res Function(_GetPicturesResponseModel) _then;
/// Create a copy of GetPicturesResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? items = null,}) {
return _then(_GetPicturesResponseModel(
items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
as List<GetPicturesItemResponseModel>,
));
}
}
/// @nodoc
mixin _$GetPicturesItemResponseModel {
String get id; String get deviceIdentificator; String? get imgType; String? get timestamp; String get fileId; String? get fileName; String? get contentType; int get createdAt;
/// Create a copy of GetPicturesItemResponseModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$GetPicturesItemResponseModelCopyWith<GetPicturesItemResponseModel> get copyWith => _$GetPicturesItemResponseModelCopyWithImpl<GetPicturesItemResponseModel>(this as GetPicturesItemResponseModel, _$identity);
/// Serializes this GetPicturesItemResponseModel to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is GetPicturesItemResponseModel&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt);
@override
String toString() {
return 'GetPicturesItemResponseModel(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt)';
}
}
/// @nodoc
abstract mixin class $GetPicturesItemResponseModelCopyWith<$Res> {
factory $GetPicturesItemResponseModelCopyWith(GetPicturesItemResponseModel value, $Res Function(GetPicturesItemResponseModel) _then) = _$GetPicturesItemResponseModelCopyWithImpl;
@useResult
$Res call({
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt
});
}
/// @nodoc
class _$GetPicturesItemResponseModelCopyWithImpl<$Res>
implements $GetPicturesItemResponseModelCopyWith<$Res> {
_$GetPicturesItemResponseModelCopyWithImpl(this._self, this._then);
final GetPicturesItemResponseModel _self;
final $Res Function(GetPicturesItemResponseModel) _then;
/// Create a copy of GetPicturesItemResponseModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
as String,imgType: freezed == imgType ? _self.imgType : imgType // ignore: cast_nullable_to_non_nullable
as String?,timestamp: freezed == timestamp ? _self.timestamp : timestamp // ignore: cast_nullable_to_non_nullable
as String?,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullable_to_non_nullable
as String,fileName: freezed == fileName ? _self.fileName : fileName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// Adds pattern-matching-related methods to [GetPicturesItemResponseModel].
extension GetPicturesItemResponseModelPatterns on GetPicturesItemResponseModel {
/// 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( _GetPicturesItemResponseModel value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel() 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( _GetPicturesItemResponseModel value) $default,){
final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel():
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( _GetPicturesItemResponseModel value)? $default,){
final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel() 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( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);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( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt) $default,) {final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel():
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);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( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt)? $default,) {final _that = this;
switch (_that) {
case _GetPicturesItemResponseModel() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _GetPicturesItemResponseModel implements GetPicturesItemResponseModel {
const _GetPicturesItemResponseModel({required this.id, required this.deviceIdentificator, this.imgType, this.timestamp, required this.fileId, this.fileName, this.contentType, required this.createdAt});
factory _GetPicturesItemResponseModel.fromJson(Map<String, dynamic> json) => _$GetPicturesItemResponseModelFromJson(json);
@override final String id;
@override final String deviceIdentificator;
@override final String? imgType;
@override final String? timestamp;
@override final String fileId;
@override final String? fileName;
@override final String? contentType;
@override final int createdAt;
/// Create a copy of GetPicturesItemResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$GetPicturesItemResponseModelCopyWith<_GetPicturesItemResponseModel> get copyWith => __$GetPicturesItemResponseModelCopyWithImpl<_GetPicturesItemResponseModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$GetPicturesItemResponseModelToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _GetPicturesItemResponseModel&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt);
@override
String toString() {
return 'GetPicturesItemResponseModel(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt)';
}
}
/// @nodoc
abstract mixin class _$GetPicturesItemResponseModelCopyWith<$Res> implements $GetPicturesItemResponseModelCopyWith<$Res> {
factory _$GetPicturesItemResponseModelCopyWith(_GetPicturesItemResponseModel value, $Res Function(_GetPicturesItemResponseModel) _then) = __$GetPicturesItemResponseModelCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt
});
}
/// @nodoc
class __$GetPicturesItemResponseModelCopyWithImpl<$Res>
implements _$GetPicturesItemResponseModelCopyWith<$Res> {
__$GetPicturesItemResponseModelCopyWithImpl(this._self, this._then);
final _GetPicturesItemResponseModel _self;
final $Res Function(_GetPicturesItemResponseModel) _then;
/// Create a copy of GetPicturesItemResponseModel
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,}) {
return _then(_GetPicturesItemResponseModel(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
as String,imgType: freezed == imgType ? _self.imgType : imgType // ignore: cast_nullable_to_non_nullable
as String?,timestamp: freezed == timestamp ? _self.timestamp : timestamp // ignore: cast_nullable_to_non_nullable
as String?,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullable_to_non_nullable
as String,fileName: freezed == fileName ? _self.fileName : fileName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
// dart format on

View File

@@ -0,0 +1,47 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'get_pictures_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_GetPicturesResponseModel _$GetPicturesResponseModelFromJson(
Map<String, dynamic> json,
) => _GetPicturesResponseModel(
items: (json['items'] as List<dynamic>)
.map(
(e) => GetPicturesItemResponseModel.fromJson(e as Map<String, dynamic>),
)
.toList(),
);
Map<String, dynamic> _$GetPicturesResponseModelToJson(
_GetPicturesResponseModel instance,
) => <String, dynamic>{'items': instance.items};
_GetPicturesItemResponseModel _$GetPicturesItemResponseModelFromJson(
Map<String, dynamic> json,
) => _GetPicturesItemResponseModel(
id: json['id'] as String,
deviceIdentificator: json['deviceIdentificator'] as String,
imgType: json['imgType'] as String?,
timestamp: json['timestamp'] as String?,
fileId: json['fileId'] as String,
fileName: json['fileName'] as String?,
contentType: json['contentType'] as String?,
createdAt: (json['createdAt'] as num).toInt(),
);
Map<String, dynamic> _$GetPicturesItemResponseModelToJson(
_GetPicturesItemResponseModel instance,
) => <String, dynamic>{
'id': instance.id,
'deviceIdentificator': instance.deviceIdentificator,
'imgType': instance.imgType,
'timestamp': instance.timestamp,
'fileId': instance.fileId,
'fileName': instance.fileName,
'contentType': instance.contentType,
'createdAt': instance.createdAt,
};

View File

@@ -1,21 +0,0 @@
import 'package:device_management/src/core/data/datasources/functions_remote_datasource.dart';
import 'package:device_management/src/core/domain/repositories/functions_repository.dart';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
class FunctionsRepositoryImpl implements FunctionsRepository {
const FunctionsRepositoryImpl(this._remote);
final FunctionsRemoteDatasource _remote;
@override
Future<List<PictureEntity>> getPictures({required String userId}) async {
await Future<void>.delayed(const Duration(milliseconds: 2000));
return _remote.getPictures(userId: userId);
}
@override
Future<PictureEntity> takePicture({required String userId}) async {
await Future<void>.delayed(const Duration(milliseconds: 2000));
return _remote.takePicture(userId: userId);
}
}

View File

@@ -0,0 +1,20 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import '../../domain/repositories/pictures_repository.dart';
import '../datasources/pictures_remote_datasource.dart';
class PicturesRepositoryImpl implements PicturesRepository {
const PicturesRepositoryImpl(this._remote);
final PicturesRemoteDatasource _remote;
@override
Future<List<PictureEntity>> getPictures({required String deviceId}) {
return _remote.getPictures(deviceId: deviceId);
}
@override
Future<PictureEntity> takePicture({required String deviceId}) {
return _remote.takePicture(deviceId: deviceId);
}
}

View File

@@ -1,7 +0,0 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
abstract class FunctionsRepository {
Future<List<PictureEntity>> getPictures({required String userId});
Future<PictureEntity> takePicture({required String userId});
}

View File

@@ -0,0 +1,7 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
abstract class PicturesRepository {
Future<List<PictureEntity>> getPictures({required String deviceId});
Future<PictureEntity> takePicture({required String deviceId});
}

View File

@@ -1,9 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:device_management/src/core/data/datasources/functions_remote_datasource.dart';
import 'package:device_management/src/core/data/datasources/functions_remote_datasource_impl.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
final functionsRemoteDatasourceProvider = Provider<FunctionsRemoteDatasource>((ref) {
final questiaRepository = getIt<QuestiaRepository>();
return FunctionsRemoteDatasourceImpl(questiaRepository);
});

View File

@@ -1,9 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:device_management/src/core/providers/functions_remote_datasource_provider.dart';
import 'package:device_management/src/core/data/repositories/functions_repository_impl.dart';
import 'package:device_management/src/core/domain/repositories/functions_repository.dart';
final functionsRepositoryProvider = Provider<FunctionsRepository>((ref) {
final remote = ref.read(functionsRemoteDatasourceProvider);
return FunctionsRepositoryImpl(remote);
});

View File

@@ -0,0 +1,10 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import '../data/datasources/pictures_remote_datasource.dart';
import '../data/datasources/pictures_remote_datasource_impl.dart';
final picturesRemoteDatasourceProvider = Provider<PicturesRemoteDatasource>((ref) {
final questiaRepository = getIt<QuestiaRepository>();
return PicturesRemoteDatasourceImpl(questiaRepository);
});

View File

@@ -0,0 +1,10 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../data/repositories/pictures_repository_impl.dart';
import '../domain/repositories/pictures_repository.dart';
import 'pictures_remote_datasource_provider.dart';
final picturesRepositoryProvider = Provider<PicturesRepository>((ref) {
final remote = ref.read(picturesRemoteDatasourceProvider);
return PicturesRepositoryImpl(remote);
});

View File

@@ -27,14 +27,14 @@ class DeviceManagementScreen extends ConsumerWidget {
),
child: Column(
children: [
// AppMenuButton(
// color: theme.getColorFor(ThemeCode.legacyPrimary),
// onPressed: () =>
// navigationContract.pushTo(AppRoutes.remoteConnection),
// icon: SFIcons.connection,
// text: context.translate(I18n.remoteConnection),
// ),
// SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () =>
navigationContract.pushTo(AppRoutes.remoteConnection),
icon: SFIcons.connection,
text: context.translate(I18n.remoteConnection),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 15)),
AppMenuButton(
color: theme.getColorFor(ThemeCode.legacyPrimary),
onPressed: () =>

View File

@@ -6,9 +6,12 @@ part 'picture_entity.freezed.dart';
abstract class PictureEntity with _$PictureEntity {
const factory PictureEntity({
required String id,
required String? deviceId,
required DateTime createdAt,
required DateTime takenAt,
required String asset,
required String deviceIdentificator,
String? imgType,
String? timestamp,
required String fileId,
String? fileName,
String? contentType,
required int createdAt,
}) = _PictureEntity;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$PictureEntity {
String get id; String? get deviceId; DateTime get createdAt; DateTime get takenAt; String get asset;
String get id; String get deviceIdentificator; String? get imgType; String? get timestamp; String get fileId; String? get fileName; String? get contentType; int get createdAt;
/// Create a copy of PictureEntity
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $PictureEntityCopyWith<PictureEntity> get copyWith => _$PictureEntityCopyWithImp
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is PictureEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.takenAt, takenAt) || other.takenAt == takenAt)&&(identical(other.asset, asset) || other.asset == asset));
return identical(this, other) || (other.runtimeType == runtimeType&&other is PictureEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt));
}
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,createdAt,takenAt,asset);
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt);
@override
String toString() {
return 'PictureEntity(id: $id, deviceId: $deviceId, createdAt: $createdAt, takenAt: $takenAt, asset: $asset)';
return 'PictureEntity(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt)';
}
@@ -45,7 +45,7 @@ abstract mixin class $PictureEntityCopyWith<$Res> {
factory $PictureEntityCopyWith(PictureEntity value, $Res Function(PictureEntity) _then) = _$PictureEntityCopyWithImpl;
@useResult
$Res call({
String id, String? deviceId, DateTime createdAt, DateTime takenAt, String asset
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt
});
@@ -62,14 +62,17 @@ class _$PictureEntityCopyWithImpl<$Res>
/// Create a copy of PictureEntity
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceId = freezed,Object? createdAt = null,Object? takenAt = null,Object? asset = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,}) {
return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
as String,imgType: freezed == imgType ? _self.imgType : imgType // ignore: cast_nullable_to_non_nullable
as String?,timestamp: freezed == timestamp ? _self.timestamp : timestamp // ignore: cast_nullable_to_non_nullable
as String?,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullable_to_non_nullable
as String,fileName: freezed == fileName ? _self.fileName : fileName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,takenAt: null == takenAt ? _self.takenAt : takenAt // ignore: cast_nullable_to_non_nullable
as DateTime,asset: null == asset ? _self.asset : asset // ignore: cast_nullable_to_non_nullable
as String,
as int,
));
}
@@ -154,10 +157,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String? deviceId, DateTime createdAt, DateTime takenAt, String asset)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _PictureEntity() when $default != null:
return $default(_that.id,_that.deviceId,_that.createdAt,_that.takenAt,_that.asset);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return orElse();
}
@@ -175,10 +178,10 @@ return $default(_that.id,_that.deviceId,_that.createdAt,_that.takenAt,_that.asse
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String? deviceId, DateTime createdAt, DateTime takenAt, String asset) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt) $default,) {final _that = this;
switch (_that) {
case _PictureEntity():
return $default(_that.id,_that.deviceId,_that.createdAt,_that.takenAt,_that.asset);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
throw StateError('Unexpected subclass');
}
@@ -195,10 +198,10 @@ return $default(_that.id,_that.deviceId,_that.createdAt,_that.takenAt,_that.asse
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String? deviceId, DateTime createdAt, DateTime takenAt, String asset)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt)? $default,) {final _that = this;
switch (_that) {
case _PictureEntity() when $default != null:
return $default(_that.id,_that.deviceId,_that.createdAt,_that.takenAt,_that.asset);case _:
return $default(_that.id,_that.deviceIdentificator,_that.imgType,_that.timestamp,_that.fileId,_that.fileName,_that.contentType,_that.createdAt);case _:
return null;
}
@@ -210,14 +213,17 @@ return $default(_that.id,_that.deviceId,_that.createdAt,_that.takenAt,_that.asse
class _PictureEntity implements PictureEntity {
const _PictureEntity({required this.id, required this.deviceId, required this.createdAt, required this.takenAt, required this.asset});
const _PictureEntity({required this.id, required this.deviceIdentificator, this.imgType, this.timestamp, required this.fileId, this.fileName, this.contentType, required this.createdAt});
@override final String id;
@override final String? deviceId;
@override final DateTime createdAt;
@override final DateTime takenAt;
@override final String asset;
@override final String deviceIdentificator;
@override final String? imgType;
@override final String? timestamp;
@override final String fileId;
@override final String? fileName;
@override final String? contentType;
@override final int createdAt;
/// Create a copy of PictureEntity
/// with the given fields replaced by the non-null parameter values.
@@ -229,16 +235,16 @@ _$PictureEntityCopyWith<_PictureEntity> get copyWith => __$PictureEntityCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PictureEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.takenAt, takenAt) || other.takenAt == takenAt)&&(identical(other.asset, asset) || other.asset == asset));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PictureEntity&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&(identical(other.imgType, imgType) || other.imgType == imgType)&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.fileName, fileName) || other.fileName == fileName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt));
}
@override
int get hashCode => Object.hash(runtimeType,id,deviceId,createdAt,takenAt,asset);
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,imgType,timestamp,fileId,fileName,contentType,createdAt);
@override
String toString() {
return 'PictureEntity(id: $id, deviceId: $deviceId, createdAt: $createdAt, takenAt: $takenAt, asset: $asset)';
return 'PictureEntity(id: $id, deviceIdentificator: $deviceIdentificator, imgType: $imgType, timestamp: $timestamp, fileId: $fileId, fileName: $fileName, contentType: $contentType, createdAt: $createdAt)';
}
@@ -249,7 +255,7 @@ abstract mixin class _$PictureEntityCopyWith<$Res> implements $PictureEntityCopy
factory _$PictureEntityCopyWith(_PictureEntity value, $Res Function(_PictureEntity) _then) = __$PictureEntityCopyWithImpl;
@override @useResult
$Res call({
String id, String? deviceId, DateTime createdAt, DateTime takenAt, String asset
String id, String deviceIdentificator, String? imgType, String? timestamp, String fileId, String? fileName, String? contentType, int createdAt
});
@@ -266,14 +272,17 @@ class __$PictureEntityCopyWithImpl<$Res>
/// Create a copy of PictureEntity
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceId = freezed,Object? createdAt = null,Object? takenAt = null,Object? asset = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? deviceIdentificator = null,Object? imgType = freezed,Object? timestamp = freezed,Object? fileId = null,Object? fileName = freezed,Object? contentType = freezed,Object? createdAt = null,}) {
return _then(_PictureEntity(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,deviceId: freezed == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,deviceIdentificator: null == deviceIdentificator ? _self.deviceIdentificator : deviceIdentificator // ignore: cast_nullable_to_non_nullable
as String,imgType: freezed == imgType ? _self.imgType : imgType // ignore: cast_nullable_to_non_nullable
as String?,timestamp: freezed == timestamp ? _self.timestamp : timestamp // ignore: cast_nullable_to_non_nullable
as String?,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullable_to_non_nullable
as String,fileName: freezed == fileName ? _self.fileName : fileName // ignore: cast_nullable_to_non_nullable
as String?,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,takenAt: null == takenAt ? _self.takenAt : takenAt // ignore: cast_nullable_to_non_nullable
as DateTime,asset: null == asset ? _self.asset : asset // ignore: cast_nullable_to_non_nullable
as String,
as int,
));
}

View File

@@ -1,5 +0,0 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
abstract class GetPicturesUseCase {
Future<List<PictureEntity>> getPictures({required String userId});
}

View File

@@ -1,44 +0,0 @@
import 'package:device_management/src/core/domain/repositories/functions_repository.dart';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:device_management/src/features/remote_connection/domain/get_pictures_use_case.dart';
class GetPicturesUseCaseImpl implements GetPicturesUseCase {
GetPicturesUseCaseImpl(this._repository);
final FunctionsRepository _repository;
@override
Future<List<PictureEntity>> getPictures({required String userId}) async {
// return _repository.getPictures(userId: userId);
return [
PictureEntity(
id: '1',
deviceId: '1111',
createdAt: DateTime.now(),
asset: 'assets/shared/images/iso_sf.png',
takenAt: DateTime.now(),
),
PictureEntity(
id: '2',
deviceId: '1111',
createdAt: DateTime.now(),
asset: 'assets/shared/images/iso_sf.png',
takenAt: DateTime.now(),
),
PictureEntity(
id: '3',
deviceId: '1111',
createdAt: DateTime.now(),
asset: 'assets/shared/images/iso_sf.png',
takenAt: DateTime.now(),
),
PictureEntity(
id: '4',
deviceId: '1111',
createdAt: DateTime.now(),
asset: 'assets/shared/images/iso_sf.png',
takenAt: DateTime.now(),
),
];
}
}

View File

@@ -1,5 +0,0 @@
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
abstract class TakePictureUseCase {
Future<PictureEntity> takePicture({required String userId});
}

View File

@@ -1,14 +0,0 @@
import 'package:device_management/src/core/domain/repositories/functions_repository.dart';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:device_management/src/features/remote_connection/domain/take_picture_use_case.dart';
class TakePictureUseCaseImpl implements TakePictureUseCase {
TakePictureUseCaseImpl(this._repository);
final FunctionsRepository _repository;
@override
Future<PictureEntity> takePicture({required String userId}) {
return _repository.takePicture(userId: userId);
}
}

View File

@@ -1,9 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:device_management/src/core/providers/functions_repository_provider.dart';
import 'package:device_management/src/features/remote_connection/domain/get_pictures_use_case.dart';
import 'package:device_management/src/features/remote_connection/domain/get_pictures_use_case_impl.dart';
final getPicturesUseCaseProvider = Provider.autoDispose<GetPicturesUseCase>((ref) {
final functionsRepository = ref.read(functionsRepositoryProvider);
return GetPicturesUseCaseImpl(functionsRepository);
});

View File

@@ -1,9 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:device_management/src/core/providers/functions_repository_provider.dart';
import 'package:device_management/src/features/remote_connection/domain/take_picture_use_case.dart';
import 'package:device_management/src/features/remote_connection/domain/take_picture_use_case_impl.dart';
final takePictureUseCaseProvider = Provider.autoDispose<TakePictureUseCase>((ref) {
final functionsRepository = ref.read(functionsRepositoryProvider);
return TakePictureUseCaseImpl(functionsRepository);
});

View File

@@ -15,19 +15,32 @@ class RemoteCameraScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen(
remoteConnectionViewModelProvider.select((s) => s.successMessage),
(_, successMessage) {
if (successMessage.isNotEmpty) {
showTopSnackbar(context, message: context.translate(successMessage), type: MessageType.success);
ref.read(remoteConnectionViewModelProvider.notifier).clearSuccess();
}
},
);
final theme = ref.watch(themePortProvider);
final isLoadingPictures = ref.watch(
remoteConnectionViewModelProvider.select((s)=>s.isLoadingPictures)
remoteConnectionViewModelProvider.select((s)=>s.isLoadingPictures)
);
final isTakingPicture = ref.watch(
remoteConnectionViewModelProvider.select((s)=>s.isTakingPicture)
);
return LegacyPageLayout(
theme: theme,
title: context.translate(I18n.remoteCamera),
body: Expanded(child: isLoadingPictures
body: isLoadingPictures || isTakingPicture
? const Center(child: CircularProgressIndicator())
: const _GallerySection()
),
: const _GallerySection(),
footer: _TakePictureSection(),
);
}
@@ -74,10 +87,11 @@ class _GallerySection extends ConsumerWidget {
color: theme.getColorFor(ThemeCode.textTertiary)
))
),
child: Column(
children: [
Image.asset(pictures[index].asset),
],
child: Image.network(
pictures[index].fileId,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) =>
const Icon(Icons.broken_image, color: Colors.grey),
)
)
)
@@ -103,24 +117,7 @@ class _TakePictureSection extends ConsumerWidget {
big: EdgeInsets.symmetric(vertical: 10, horizontal: 25)
),
child: PrimaryButton(
onPressed: () async {
showDialog(context: context, builder: (context)=>Dialog(
child: Container(
padding: SizeUtils.getByScreen(
small: EdgeInsets.symmetric(horizontal: 32, vertical: 30),
big: EdgeInsets.symmetric(horizontal: 30, vertical: 28)
),
width: SizeUtils.getByScreen(small: 360, big: 350),
height: SizeUtils.getByScreen(small: 195, big: 185),
child: Center(child: Text(context.translate(I18n.loadingPhoto),
textAlign: TextAlign.center,
style: TextStyle(fontSize: SizeUtils.getByScreen(small: 26, big: 25)),
)),
),
));
await vm.takePicture();
Navigator.pop(context);
},
onPressed: vm.takePicture,
text: context.translate(I18n.takePicture),
color: theme.getColorFor(ThemeCode.legacyPrimary),
height: SizeUtils.getByScreen(small: 36, big: 35),

View File

@@ -1,13 +1,12 @@
import 'package:device_management/src/core/providers/pictures_repository_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:device_management/src/features/remote_connection/domain/get_pictures_use_case.dart';
import 'package:device_management/src/features/remote_connection/domain/take_picture_use_case.dart';
import 'package:device_management/src/features/remote_connection/presentation/providers/get_pictures_use_case_provider.dart';
import 'package:device_management/src/features/remote_connection/presentation/providers/take_picture_use_case_provider.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_state.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:sf_localizations/sf_localizations.dart';
import '../../../../core/domain/repositories/pictures_repository.dart';
final remoteConnectionViewModelProvider =
NotifierProvider.autoDispose<RemoteConnectionViewModel, RemoteConnectionViewState>(
@@ -15,17 +14,17 @@ NotifierProvider.autoDispose<RemoteConnectionViewModel, RemoteConnectionViewStat
);
class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
late final GetPicturesUseCase _getPicturesUseCase;
late final TakePictureUseCase _takePictureUseCase;
late final TextEditingController phoneController;
late final CommandsRepository _commandsRepository;
late final PicturesRepository _picturesRepository;
static final RegExp _phoneRegex = RegExp(r'^\+?\d{6,15}$');
@override
RemoteConnectionViewState build() {
_getPicturesUseCase = ref.read(getPicturesUseCaseProvider);
_takePictureUseCase = ref.read(takePictureUseCaseProvider);
_commandsRepository = ref.read(commandsRepositoryProvider);
_picturesRepository = ref.read(picturesRepositoryProvider);
phoneController = TextEditingController();
phoneController.addListener(_onPhoneChanged);
@@ -38,17 +37,23 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
}
Future<void> load() async {
final user = await ref.read(userInfoProvider.future);
final device = ref.read(selectedDeviceProvider);
if (device == null) return;
final pictures = await _getPicturesUseCase.getPictures(userId: user.id);
setImages(pictures);
}
state = state.copyWith(deviceId: device.identificator);
void setImages(List<PictureEntity> pictures) {
state = state.copyWith(
pictures: pictures,
isLoadingPictures: false,
);
try {
final pictures = await _picturesRepository.getPictures(deviceId: device.identificator);
if (!ref.mounted) return;
state = state.copyWith(
pictures: pictures,
isLoadingPictures: false,
);
} catch (_) {
if (!ref.mounted) return;
state = state.copyWith(isLoadingPictures: false);
}
}
void _onPhoneChanged() {
@@ -58,6 +63,11 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
state = state.copyWith(phone: text, errorMessage: '');
}
void updateDialCode(String value) {
if (value == state.dialCode) return;
state = state.copyWith(dialCode: value, errorMessage: '');
}
void prevPicture() {
int pictureIndex = state.pictureIndex - 1;
@@ -84,19 +94,39 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
);
}
void clearSuccess() {
state = state.copyWith(
successMessage: '',
);
}
Future<void> takePicture() async {
try {
state = state.copyWith(isTakingPicture: true);
state = state.copyWith(
isTakingPicture: true,
successMessage: '',
);
final request = SendCommandRequestModel(
device: state.deviceId,
command: DeviceCommand.requestPhoto,
);
await _takePictureUseCase.takePicture(userId: '')
.then((picture) {
List<PictureEntity> pictures = state.pictures;
pictures.add(picture);
state = state.copyWith(
isTakingPicture: true,
);
});
} catch (e){
await _commandsRepository.send(request: request);
if (!ref.mounted) return;
final pictures = await _picturesRepository.getPictures(
deviceId: state.deviceId,
);
if (!ref.mounted) return;
state = state.copyWith(
isTakingPicture: false,
pictures: pictures,
successMessage: I18n.photoTaken,
);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isTakingPicture: false,
errorMessage: e.toString(),
@@ -106,14 +136,36 @@ class RemoteConnectionViewModel extends Notifier<RemoteConnectionViewState> {
Future<void> call() async {
final phone = phoneController.text;
final dialCode = state.dialCode;
if (phone.isEmpty){
state = state.copyWith(errorMessage: 'errorMessagePhoneIsEmpty');
state = state.copyWith(errorMessage: I18n.errorMessagePhoneIsInvalid);
return;
}
if (!_phoneRegex.hasMatch(phone)) {
state = state.copyWith(errorMessage: 'errorMessagePhoneIsInvalid');
state = state.copyWith(errorMessage: I18n.errorMessagePhoneIsInvalid);
return;
}
try {
state = state.copyWith(isCalling: true);
final fullPhone = dialCode + phone;
final request = SendCommandRequestModel(
device: state.deviceId,
command: DeviceCommand.callCenter,
data: {'phone_number': fullPhone},
);
await _commandsRepository.send(request: request);
if (!ref.mounted) return;
state = state.copyWith(isCalling: false);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isCalling: false,
errorMessage: e.toString(),
);
}
}
void disposeControllers() {

View File

@@ -6,12 +6,15 @@ part 'remote_connection_view_state.freezed.dart';
@freezed
abstract class RemoteConnectionViewState with _$RemoteConnectionViewState {
const factory RemoteConnectionViewState({
@Default('') String deviceId,
@Default('+34') String dialCode,
@Default('') String phone,
@Default([]) List<PictureEntity> pictures,
@Default(0) int pictureIndex,
@Default(true) bool isLoadingPictures,
@Default(false) bool isTakingPicture,
@Default(false) bool isCalling,
@Default('') String errorMessage
@Default('') String errorMessage,
@Default('') String successMessage
}) = _RemoteConnectionViewState;
}

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$RemoteConnectionViewState {
String get phone; List<PictureEntity> get pictures; int get pictureIndex; bool get isLoadingPictures; bool get isTakingPicture; bool get isCalling; String get errorMessage;
String get deviceId; String get dialCode; String get phone; List<PictureEntity> get pictures; int get pictureIndex; bool get isLoadingPictures; bool get isTakingPicture; bool get isCalling; String get errorMessage; String get successMessage;
/// Create a copy of RemoteConnectionViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $RemoteConnectionViewStateCopyWith<RemoteConnectionViewState> get copyWith => _$
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteConnectionViewState&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other.pictures, pictures)&&(identical(other.pictureIndex, pictureIndex) || other.pictureIndex == pictureIndex)&&(identical(other.isLoadingPictures, isLoadingPictures) || other.isLoadingPictures == isLoadingPictures)&&(identical(other.isTakingPicture, isTakingPicture) || other.isTakingPicture == isTakingPicture)&&(identical(other.isCalling, isCalling) || other.isCalling == isCalling)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteConnectionViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other.pictures, pictures)&&(identical(other.pictureIndex, pictureIndex) || other.pictureIndex == pictureIndex)&&(identical(other.isLoadingPictures, isLoadingPictures) || other.isLoadingPictures == isLoadingPictures)&&(identical(other.isTakingPicture, isTakingPicture) || other.isTakingPicture == isTakingPicture)&&(identical(other.isCalling, isCalling) || other.isCalling == isCalling)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage));
}
@override
int get hashCode => Object.hash(runtimeType,phone,const DeepCollectionEquality().hash(pictures),pictureIndex,isLoadingPictures,isTakingPicture,isCalling,errorMessage);
int get hashCode => Object.hash(runtimeType,deviceId,dialCode,phone,const DeepCollectionEquality().hash(pictures),pictureIndex,isLoadingPictures,isTakingPicture,isCalling,errorMessage,successMessage);
@override
String toString() {
return 'RemoteConnectionViewState(phone: $phone, pictures: $pictures, pictureIndex: $pictureIndex, isLoadingPictures: $isLoadingPictures, isTakingPicture: $isTakingPicture, isCalling: $isCalling, errorMessage: $errorMessage)';
return 'RemoteConnectionViewState(deviceId: $deviceId, dialCode: $dialCode, phone: $phone, pictures: $pictures, pictureIndex: $pictureIndex, isLoadingPictures: $isLoadingPictures, isTakingPicture: $isTakingPicture, isCalling: $isCalling, errorMessage: $errorMessage, successMessage: $successMessage)';
}
@@ -45,7 +45,7 @@ abstract mixin class $RemoteConnectionViewStateCopyWith<$Res> {
factory $RemoteConnectionViewStateCopyWith(RemoteConnectionViewState value, $Res Function(RemoteConnectionViewState) _then) = _$RemoteConnectionViewStateCopyWithImpl;
@useResult
$Res call({
String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage
String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage
});
@@ -62,15 +62,18 @@ class _$RemoteConnectionViewStateCopyWithImpl<$Res>
/// Create a copy of RemoteConnectionViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? phone = null,Object? pictures = null,Object? pictureIndex = null,Object? isLoadingPictures = null,Object? isTakingPicture = null,Object? isCalling = null,Object? errorMessage = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? dialCode = null,Object? phone = null,Object? pictures = null,Object? pictureIndex = null,Object? isLoadingPictures = null,Object? isTakingPicture = null,Object? isCalling = null,Object? errorMessage = null,Object? successMessage = null,}) {
return _then(_self.copyWith(
phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
as String,pictures: null == pictures ? _self.pictures : pictures // ignore: cast_nullable_to_non_nullable
as List<PictureEntity>,pictureIndex: null == pictureIndex ? _self.pictureIndex : pictureIndex // ignore: cast_nullable_to_non_nullable
as int,isLoadingPictures: null == isLoadingPictures ? _self.isLoadingPictures : isLoadingPictures // ignore: cast_nullable_to_non_nullable
as bool,isTakingPicture: null == isTakingPicture ? _self.isTakingPicture : isTakingPicture // ignore: cast_nullable_to_non_nullable
as bool,isCalling: null == isCalling ? _self.isCalling : isCalling // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,successMessage: null == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}
@@ -156,10 +159,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _RemoteConnectionViewState() when $default != null:
return $default(_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage);case _:
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage,_that.successMessage);case _:
return orElse();
}
@@ -177,10 +180,10 @@ return $default(_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPic
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage) $default,) {final _that = this;
switch (_that) {
case _RemoteConnectionViewState():
return $default(_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage);case _:
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage,_that.successMessage);case _:
throw StateError('Unexpected subclass');
}
@@ -197,10 +200,10 @@ return $default(_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPic
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage)? $default,) {final _that = this;
switch (_that) {
case _RemoteConnectionViewState() when $default != null:
return $default(_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage);case _:
return $default(_that.deviceId,_that.dialCode,_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPictures,_that.isTakingPicture,_that.isCalling,_that.errorMessage,_that.successMessage);case _:
return null;
}
@@ -212,9 +215,11 @@ return $default(_that.phone,_that.pictures,_that.pictureIndex,_that.isLoadingPic
class _RemoteConnectionViewState implements RemoteConnectionViewState {
const _RemoteConnectionViewState({this.phone = '', final List<PictureEntity> pictures = const [], this.pictureIndex = 0, this.isLoadingPictures = true, this.isTakingPicture = false, this.isCalling = false, this.errorMessage = ''}): _pictures = pictures;
const _RemoteConnectionViewState({this.deviceId = '', this.dialCode = '+34', this.phone = '', final List<PictureEntity> pictures = const [], this.pictureIndex = 0, this.isLoadingPictures = true, this.isTakingPicture = false, this.isCalling = false, this.errorMessage = '', this.successMessage = ''}): _pictures = pictures;
@override@JsonKey() final String deviceId;
@override@JsonKey() final String dialCode;
@override@JsonKey() final String phone;
final List<PictureEntity> _pictures;
@override@JsonKey() List<PictureEntity> get pictures {
@@ -228,6 +233,7 @@ class _RemoteConnectionViewState implements RemoteConnectionViewState {
@override@JsonKey() final bool isTakingPicture;
@override@JsonKey() final bool isCalling;
@override@JsonKey() final String errorMessage;
@override@JsonKey() final String successMessage;
/// Create a copy of RemoteConnectionViewState
/// with the given fields replaced by the non-null parameter values.
@@ -239,16 +245,16 @@ _$RemoteConnectionViewStateCopyWith<_RemoteConnectionViewState> get copyWith =>
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteConnectionViewState&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other._pictures, _pictures)&&(identical(other.pictureIndex, pictureIndex) || other.pictureIndex == pictureIndex)&&(identical(other.isLoadingPictures, isLoadingPictures) || other.isLoadingPictures == isLoadingPictures)&&(identical(other.isTakingPicture, isTakingPicture) || other.isTakingPicture == isTakingPicture)&&(identical(other.isCalling, isCalling) || other.isCalling == isCalling)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteConnectionViewState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.dialCode, dialCode) || other.dialCode == dialCode)&&(identical(other.phone, phone) || other.phone == phone)&&const DeepCollectionEquality().equals(other._pictures, _pictures)&&(identical(other.pictureIndex, pictureIndex) || other.pictureIndex == pictureIndex)&&(identical(other.isLoadingPictures, isLoadingPictures) || other.isLoadingPictures == isLoadingPictures)&&(identical(other.isTakingPicture, isTakingPicture) || other.isTakingPicture == isTakingPicture)&&(identical(other.isCalling, isCalling) || other.isCalling == isCalling)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.successMessage, successMessage) || other.successMessage == successMessage));
}
@override
int get hashCode => Object.hash(runtimeType,phone,const DeepCollectionEquality().hash(_pictures),pictureIndex,isLoadingPictures,isTakingPicture,isCalling,errorMessage);
int get hashCode => Object.hash(runtimeType,deviceId,dialCode,phone,const DeepCollectionEquality().hash(_pictures),pictureIndex,isLoadingPictures,isTakingPicture,isCalling,errorMessage,successMessage);
@override
String toString() {
return 'RemoteConnectionViewState(phone: $phone, pictures: $pictures, pictureIndex: $pictureIndex, isLoadingPictures: $isLoadingPictures, isTakingPicture: $isTakingPicture, isCalling: $isCalling, errorMessage: $errorMessage)';
return 'RemoteConnectionViewState(deviceId: $deviceId, dialCode: $dialCode, phone: $phone, pictures: $pictures, pictureIndex: $pictureIndex, isLoadingPictures: $isLoadingPictures, isTakingPicture: $isTakingPicture, isCalling: $isCalling, errorMessage: $errorMessage, successMessage: $successMessage)';
}
@@ -259,7 +265,7 @@ abstract mixin class _$RemoteConnectionViewStateCopyWith<$Res> implements $Remot
factory _$RemoteConnectionViewStateCopyWith(_RemoteConnectionViewState value, $Res Function(_RemoteConnectionViewState) _then) = __$RemoteConnectionViewStateCopyWithImpl;
@override @useResult
$Res call({
String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage
String deviceId, String dialCode, String phone, List<PictureEntity> pictures, int pictureIndex, bool isLoadingPictures, bool isTakingPicture, bool isCalling, String errorMessage, String successMessage
});
@@ -276,15 +282,18 @@ class __$RemoteConnectionViewStateCopyWithImpl<$Res>
/// Create a copy of RemoteConnectionViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? phone = null,Object? pictures = null,Object? pictureIndex = null,Object? isLoadingPictures = null,Object? isTakingPicture = null,Object? isCalling = null,Object? errorMessage = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? dialCode = null,Object? phone = null,Object? pictures = null,Object? pictureIndex = null,Object? isLoadingPictures = null,Object? isTakingPicture = null,Object? isCalling = null,Object? errorMessage = null,Object? successMessage = null,}) {
return _then(_RemoteConnectionViewState(
phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,dialCode: null == dialCode ? _self.dialCode : dialCode // ignore: cast_nullable_to_non_nullable
as String,phone: null == phone ? _self.phone : phone // ignore: cast_nullable_to_non_nullable
as String,pictures: null == pictures ? _self._pictures : pictures // ignore: cast_nullable_to_non_nullable
as List<PictureEntity>,pictureIndex: null == pictureIndex ? _self.pictureIndex : pictureIndex // ignore: cast_nullable_to_non_nullable
as int,isLoadingPictures: null == isLoadingPictures ? _self.isLoadingPictures : isLoadingPictures // ignore: cast_nullable_to_non_nullable
as bool,isTakingPicture: null == isTakingPicture ? _self.isTakingPicture : isTakingPicture // ignore: cast_nullable_to_non_nullable
as bool,isCalling: null == isCalling ? _self.isCalling : isCalling // ignore: cast_nullable_to_non_nullable
as bool,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
as String,successMessage: null == successMessage ? _self.successMessage : successMessage // ignore: cast_nullable_to_non_nullable
as String,
));
}

View File

@@ -1,38 +1,53 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:device_management/src/features/remote_connection/domain/entities/picture_entity.dart';
import 'package:device_management/src/features/remote_connection/presentation/state/remote_connection_view_model.dart';
import 'package:utils/utils.dart';
class ShowPictureDialog extends ConsumerWidget {
const ShowPictureDialog();
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.read(themePortProvider);
final viewModel = ref.read(remoteConnectionViewModelProvider.notifier);
final pictures = ref.watch(
remoteConnectionViewModelProvider.select((s)=>s.pictures)
remoteConnectionViewModelProvider.select((s) => s.pictures),
);
final pictureIndex = ref.watch(
remoteConnectionViewModelProvider.select((s)=>s.pictureIndex)
remoteConnectionViewModelProvider.select((s) => s.pictureIndex),
);
if (pictures.isEmpty) {
return Container(
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
height: SizeUtils.getByScreen(small: 200, big: 190),
child: Center(
child: Text(context.translate(I18n.noPhotosAvailable),
style: const TextStyle(color: Colors.grey)),
),
);
}
final picture = pictures[pictureIndex];
return Container(
decoration: BoxDecoration(
color: theme.getColorFor(ThemeCode.backgroundPrimary),
borderRadius: BorderRadius.all(Radius.circular(8))
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
height: SizeUtils.getByScreen(small: 350, big: 340),
child: Column(
children: [
_PictureSection(asset: pictures[pictureIndex].asset),
_MetadataSection(picture: pictures[pictureIndex]),
Expanded(
child: _PictureSection(fileId: picture.fileId),
),
_MetadataSection(picture: picture),
_ControlsSection(
prev: viewModel.prevPicture,
next: viewModel.nextPicture,
@@ -43,63 +58,64 @@ class ShowPictureDialog extends ConsumerWidget {
}
}
class _PictureSection extends ConsumerWidget {
class _PictureSection extends StatelessWidget {
final String fileId;
final String asset;
const _PictureSection({
required this.asset,
});
const _PictureSection({required this.fileId});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Expanded(
child: Center(
child: Image.asset(asset),
)
Widget build(BuildContext context) {
return Center(
child: Image.network(
fileId,
fit: BoxFit.contain,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.broken_image, size: 64, color: Colors.grey);
},
),
);
}
}
class _MetadataSection extends ConsumerWidget {
class _MetadataSection extends StatelessWidget {
final PictureEntity picture;
const _MetadataSection({
required this.picture,
});
const _MetadataSection({required this.picture});
@override
Widget build(BuildContext context, WidgetRef ref) {
Widget build(BuildContext context) {
final date = DateTime.fromMillisecondsSinceEpoch(picture.createdAt);
final dateStr =
'${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year} '
'${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
return Column(
children: [
Text(picture.createdAt.toString())
],
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Text(dateStr, style: TextStyle(fontSize: 13, color: Colors.grey.shade600)),
);
}
}
class _ControlsSection extends ConsumerWidget {
class _ControlsSection extends StatelessWidget {
final VoidCallback prev;
final VoidCallback next;
const _ControlsSection({
required this.prev,
required this.next
});
const _ControlsSection({required this.prev, required this.next});
@override
Widget build(BuildContext context, WidgetRef ref) {
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(onPressed: prev, icon: Icon(Icons.arrow_back_ios_new_rounded)),
IconButton(onPressed: next, icon: Icon(Icons.arrow_forward_ios_rounded)),
IconButton(
onPressed: prev, icon: const Icon(Icons.arrow_back_ios_new_rounded)),
IconButton(
onPressed: next, icon: const Icon(Icons.arrow_forward_ios_rounded)),
],
);
}
}
}

View File

@@ -6,47 +6,50 @@ import 'package:sf_localizations/sf_localizations.dart';
import 'package:utils/utils.dart';
class SpyCallDialog extends ConsumerWidget {
Future<void> _onCall(BuildContext context, WidgetRef ref) async {
final vm = ref.read(remoteConnectionViewModelProvider.notifier);
await vm.call();
if (!context.mounted) return;
final errorMessage = ref.read(
remoteConnectionViewModelProvider.select((s)=>s.errorMessage)
);
if (errorMessage.isNotEmpty) return;
Navigator.pop(context);
}
const SpyCallDialog();
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final vm = ref.read(remoteConnectionViewModelProvider.notifier);
ref.listen(remoteConnectionViewModelProvider.select((s) => s.errorMessage),
(_, msg) {
if (msg.isNotEmpty) {
showTopSnackbar(context,
message: context.translate(msg), type: MessageType.error);
}
});
ref.listen(remoteConnectionViewModelProvider.select((s) => s.isCalling),
(prev, isCalling) {
if (prev == true && !isCalling) {
final error = ref.read(remoteConnectionViewModelProvider).errorMessage;
if (error.isEmpty && context.mounted) {
Navigator.pop(context);
showTopSnackbar(context,
message: context.translate(I18n.remoteListening),
type: MessageType.success);
}
}
});
return Container(
padding: SizeUtils.getByScreen(
small: EdgeInsets.symmetric(horizontal: 26, vertical: 20),
big: EdgeInsets.symmetric(horizontal: 24, vertical: 18)
),
small: const EdgeInsets.symmetric(horizontal: 26, vertical: 20),
big: const EdgeInsets.symmetric(horizontal: 24, vertical: 18)),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
color: theme.getColorFor(ThemeCode.backgroundSecondary)
),
borderRadius: const BorderRadius.all(Radius.circular(8)),
color: theme.getColorFor(ThemeCode.backgroundSecondary)),
width: SizeUtils.getByScreen(small: 390, big: 380),
height: SizeUtils.getByScreen(small: 250, big: 243),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_Header(theme: theme),
SizedBox(height: SizeUtils.getByScreen(small: 8, big: 7)),
_PhoneSection(onSubmit: () {_onCall(context, ref);}),
const _ErrorMessageSection(),
_PhoneSection(onSubmit: vm.call),
SizedBox(height: SizeUtils.getByScreen(small: 28, big: 27)),
_CallSection(onPressed: () {_onCall(context, ref);}),
_CallSection(onPressed: vm.call),
],
),
);
@@ -54,103 +57,90 @@ class SpyCallDialog extends ConsumerWidget {
}
class _Header extends StatelessWidget {
final ThemePort theme;
const _Header({
required this.theme,
});
const _Header({required this.theme});
@override
Widget build(BuildContext context) {
return Stack(
children: [
Center(child: Text(context.translate(I18n.remoteListening),
Center(
child: Text(
context.translate(I18n.remoteListening),
textAlign: TextAlign.center,
style: TextStyle(fontSize: SizeUtils.getByScreen(small: 19, big: 18)),
style:
TextStyle(fontSize: SizeUtils.getByScreen(small: 19, big: 18)),
)),
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: (){Navigator.pop(context);},
icon: Icon(Icons.close, color: theme.getColorFor(ThemeCode.legacyPrimary)),
)
)
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () => Navigator.pop(context),
icon: Icon(Icons.close,
color: theme.getColorFor(ThemeCode.legacyPrimary)),
))
],
);
}
}
class _PhoneSection extends ConsumerWidget {
final VoidCallback onSubmit;
const _PhoneSection({
required this.onSubmit,
});
const _PhoneSection({required this.onSubmit});
@override
Widget build(BuildContext context, WidgetRef ref) {
final vm = ref.read(remoteConnectionViewModelProvider.notifier);
return CustomTextField(
controller: vm.phoneController,
hint: context.translate(I18n.insertPhone),
keyboardType: TextInputType.number,
onSubmitted: (_) => onSubmit(),
final dialCode = ref.watch(
remoteConnectionViewModelProvider.select((s) => s.dialCode),
);
}
}
class _ErrorMessageSection extends ConsumerWidget {
const _ErrorMessageSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
final viewState = ref.watch(remoteConnectionViewModelProvider);
if (viewState.errorMessage.isNotEmpty) {
return Column(
children: [
const SizedBox(height: 4),
Text(
viewState.errorMessage,
textAlign: TextAlign.center,
style: const TextStyle(
color: Color.fromRGBO(239, 17, 17, 1),
fontSize: 12,
),
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CountryPrefixPicker(
headerText: context.translate(I18n.selectYourCountry),
initialSelection: dialCode,
onChanged: (country) {
vm.updateDialCode(country.dialCode ?? dialCode);
},
width: 80,
),
SizedBox(width: SizeUtils.getByScreen(small: 8, big: 7)),
Expanded(
child: CustomTextField(
controller: vm.phoneController,
hint: context.translate(I18n.insertPhone),
keyboardType: TextInputType.phone,
onSubmitted: (_) => onSubmit(),
),
],
);
} else return SizedBox.shrink();
),
],
);
}
}
class _CallSection extends ConsumerWidget {
final VoidCallback onPressed;
const _CallSection({
required this.onPressed,
});
const _CallSection({required this.onPressed});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.read(themePortProvider);
return PrimaryButton(
onPressed: onPressed,
text: context.translate(I18n.call),
color: theme.getColorFor(ThemeCode.legacyPrimary),
height: SizeUtils.getByScreen(small: 38, big: 36),
radius: SizeUtils.getByScreen(small: 32, big: 34),
final isCalling = ref.watch(
remoteConnectionViewModelProvider.select((s) => s.isCalling),
);
}
}
return isCalling
? const Center(child: CircularProgressIndicator())
: PrimaryButton(
onPressed: onPressed,
text: context.translate(I18n.call),
color: theme.getColorFor(ThemeCode.legacyPrimary),
height: SizeUtils.getByScreen(small: 38, big: 36),
radius: SizeUtils.getByScreen(small: 32, big: 34),
);
}
}

View File

@@ -4,10 +4,14 @@ part 'send_command_request_model.freezed.dart';
part 'send_command_request_model.g.dart';
enum DeviceCommand {
@JsonValue('CALL_CENTER')
callCenter,
@JsonValue('FACTORY')
factory,
@JsonValue('FIND_DEVICE')
findDevice,
@JsonValue('REQUEST_PHOTO')
requestPhoto,
@JsonValue('RESTART')
restart,
@JsonValue('REWARDS')

View File

@@ -23,8 +23,10 @@ Map<String, dynamic> _$SendCommandRequestModelToJson(
};
const _$DeviceCommandEnumMap = {
DeviceCommand.callCenter: 'CALL_CENTER',
DeviceCommand.factory: 'FACTORY',
DeviceCommand.findDevice: 'FIND_DEVICE',
DeviceCommand.requestPhoto: 'REQUEST_PHOTO',
DeviceCommand.restart: 'RESTART',
DeviceCommand.rewards: 'REWARDS',
DeviceCommand.setLanguage: 'SET_LANGUAGE',

View File

@@ -623,5 +623,7 @@
"volumeRingtone": "Klingeltonlautstärke",
"volumeAlarm": "Alarmlautstärke",
"volumeHint": "Sie können den Schieberegler ziehen, um die Gerätelautstärke anzupassen. Die App speichert nur die zuletzt erfolgreich eingestellte Lautstärke. Die tatsächliche Lautstärke hängt vom Gerät ab.",
"volumeSend": "Senden"
"volumeSend": "Senden",
"photoTaken": "Foto erfolgreich aufgenommen",
"noPhotosAvailable": "Keine Fotos verfügbar"
}

View File

@@ -755,5 +755,7 @@
"vibrationOnly": "Vibration only",
"silent": "Silent",
"syncClockMessage": "Synchronize the device clock with the current time",
"locationWifiNetworksOptional": "WiFi networks (optional)"
"locationWifiNetworksOptional": "WiFi networks (optional)",
"photoTaken": "Photo taken successfully",
"noPhotosAvailable": "No photos available"
}

View File

@@ -753,5 +753,7 @@
"vibrationOnly": "Solo vibración",
"silent": "Silencio",
"syncClockMessage": "Sincroniza el reloj del dispositivo con la hora actual",
"locationWifiNetworksOptional": "Redes WiFi (opcional)"
"locationWifiNetworksOptional": "Redes WiFi (opcional)",
"photoTaken": "Foto tomada exitosamente",
"noPhotosAvailable": "No hay fotos disponibles"
}

View File

@@ -623,5 +623,7 @@
"volumeRingtone": "Volume de la sonnerie",
"volumeAlarm": "Volume de l'alarme",
"volumeHint": "Vous pouvez faire glisser le curseur pour régler le volume de l'appareil. L'application ne sauvegarde que le dernier niveau de volume ajusté avec succès. Le volume réel dépendra de l'appareil.",
"volumeSend": "Envoyer"
"volumeSend": "Envoyer",
"photoTaken": "Photo prise avec succès",
"noPhotosAvailable": "Aucune photo disponible"
}

View File

@@ -623,5 +623,7 @@
"volumeRingtone": "Volume suoneria",
"volumeAlarm": "Volume sveglia",
"volumeHint": "Puoi trascinare il cursore per regolare il volume del dispositivo. L'app salva solo l'ultimo livello di volume regolato con successo. Il volume effettivo dipenderà dal dispositivo.",
"volumeSend": "Invia"
"volumeSend": "Invia",
"photoTaken": "Foto scattata con successo",
"noPhotosAvailable": "Nessuna foto disponibile"
}

View File

@@ -623,5 +623,7 @@
"volumeRingtone": "Volume do toque",
"volumeAlarm": "Volume do alarme",
"volumeHint": "Você pode arrastar o controle deslizante para ajustar o volume do dispositivo. O aplicativo salva apenas o nível de volume ajustado com sucesso mais recentemente. O volume real dependerá do dispositivo.",
"volumeSend": "Enviar"
"volumeSend": "Enviar",
"photoTaken": "Foto tirada com sucesso",
"noPhotosAvailable": "Nenhuma foto disponível"
}

View File

@@ -759,5 +759,7 @@ class I18n {
static const String wifiSettings = 'wifiSettings';
static const String wifiSsid = 'wifiSsid';
static const String wifiSsidHint = 'wifiSsidHint';
static const String photoTaken = 'photoTaken';
static const String noPhotosAvailable = 'noPhotosAvailable';
static const String yesterday = 'yesterday';
}