feat(device-management): add installed apps enable/disable feature

This commit is contained in:
2026-04-26 05:12:42 +02:00
parent 01cb4c9427
commit 853b6f20a3
16 changed files with 1456 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
import 'package:device_management/src/core/data/models/installed_apps_response_dto.dart';
abstract class InstalledAppsRemoteDatasource {
Future<InstalledAppsResponseDto> getInstalledApps({
required String identificator,
});
Future<InstalledAppsResponseDto> updateInstalledApps({
required String identificator,
required List<Map<String, dynamic>> apps,
});
}

View File

@@ -0,0 +1,59 @@
import 'package:device_management/src/core/data/datasources/installed_apps_remote_datasource.dart';
import 'package:device_management/src/core/data/models/installed_apps_response_dto.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
class InstalledAppsRemoteDatasourceImpl implements InstalledAppsRemoteDatasource {
InstalledAppsRemoteDatasourceImpl(this._repository);
final SaveFamilyRepository _repository;
@override
Future<InstalledAppsResponseDto> getInstalledApps({
required String identificator,
}) async {
final response = await safeCall(
() => _repository.get<Map<String, dynamic>>(
'/devices/identificator/$identificator/installed-apps',
),
'Error getting installed apps',
);
final data = response.data;
if (data == null || data.isEmpty) {
throw const ApiException(message: 'Empty response', statusCode: 404);
}
final item = data['item'] as Map<String, dynamic>?;
if (item == null) {
throw const ApiException(message: 'No item in response', statusCode: 404);
}
return InstalledAppsResponseDto.fromJson(item);
}
@override
Future<InstalledAppsResponseDto> updateInstalledApps({
required String identificator,
required List<Map<String, dynamic>> apps,
}) async {
final response = await safeCall(
() => _repository.put<Map<String, dynamic>>(
'/devices/identificator/$identificator/installed-apps',
body: <String, dynamic>{'apps': apps},
),
'Error updating installed apps',
);
final data = response.data;
if (data == null || data.isEmpty) {
throw const ApiException(message: 'Empty response');
}
final item = data['item'] as Map<String, dynamic>?;
if (item == null) {
throw const ApiException(message: 'No item in response');
}
return InstalledAppsResponseDto.fromJson(item);
}
}

View File

@@ -0,0 +1,45 @@
import 'package:device_management/src/core/domain/entities/installed_app_entity.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'installed_apps_response_dto.freezed.dart';
part 'installed_apps_response_dto.g.dart';
@freezed
abstract class InstalledAppsResponseDto with _$InstalledAppsResponseDto {
const factory InstalledAppsResponseDto({
required String id,
required String deviceIdentificator,
required List<InstalledAppItemDto> apps,
required int createdAt,
int? updatedAt,
}) = _InstalledAppsResponseDto;
factory InstalledAppsResponseDto.fromJson(Map<String, dynamic> json) =>
_$InstalledAppsResponseDtoFromJson(json);
}
@freezed
abstract class InstalledAppItemDto with _$InstalledAppItemDto {
const factory InstalledAppItemDto({
required String appUid,
required String appName,
required bool isEnabled,
}) = _InstalledAppItemDto;
factory InstalledAppItemDto.fromJson(Map<String, dynamic> json) =>
_$InstalledAppItemDtoFromJson(json);
}
extension InstalledAppsResponseDtoX on InstalledAppsResponseDto {
List<InstalledAppEntity> toEntities() {
return apps
.map(
(a) => InstalledAppEntity(
appUid: a.appUid,
appName: a.appName,
isEnabled: a.isEnabled,
),
)
.toList();
}
}

View File

@@ -0,0 +1,564 @@
// 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 'installed_apps_response_dto.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$InstalledAppsResponseDto {
String get id; String get deviceIdentificator; List<InstalledAppItemDto> get apps; int get createdAt; int? get updatedAt;
/// Create a copy of InstalledAppsResponseDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$InstalledAppsResponseDtoCopyWith<InstalledAppsResponseDto> get copyWith => _$InstalledAppsResponseDtoCopyWithImpl<InstalledAppsResponseDto>(this as InstalledAppsResponseDto, _$identity);
/// Serializes this InstalledAppsResponseDto to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is InstalledAppsResponseDto&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&const DeepCollectionEquality().equals(other.apps, apps)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,const DeepCollectionEquality().hash(apps),createdAt,updatedAt);
@override
String toString() {
return 'InstalledAppsResponseDto(id: $id, deviceIdentificator: $deviceIdentificator, apps: $apps, createdAt: $createdAt, updatedAt: $updatedAt)';
}
}
/// @nodoc
abstract mixin class $InstalledAppsResponseDtoCopyWith<$Res> {
factory $InstalledAppsResponseDtoCopyWith(InstalledAppsResponseDto value, $Res Function(InstalledAppsResponseDto) _then) = _$InstalledAppsResponseDtoCopyWithImpl;
@useResult
$Res call({
String id, String deviceIdentificator, List<InstalledAppItemDto> apps, int createdAt, int? updatedAt
});
}
/// @nodoc
class _$InstalledAppsResponseDtoCopyWithImpl<$Res>
implements $InstalledAppsResponseDtoCopyWith<$Res> {
_$InstalledAppsResponseDtoCopyWithImpl(this._self, this._then);
final InstalledAppsResponseDto _self;
final $Res Function(InstalledAppsResponseDto) _then;
/// Create a copy of InstalledAppsResponseDto
/// 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? apps = null,Object? createdAt = null,Object? updatedAt = freezed,}) {
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,apps: null == apps ? _self.apps : apps // ignore: cast_nullable_to_non_nullable
as List<InstalledAppItemDto>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// Adds pattern-matching-related methods to [InstalledAppsResponseDto].
extension InstalledAppsResponseDtoPatterns on InstalledAppsResponseDto {
/// 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( _InstalledAppsResponseDto value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _InstalledAppsResponseDto() 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( _InstalledAppsResponseDto value) $default,){
final _that = this;
switch (_that) {
case _InstalledAppsResponseDto():
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( _InstalledAppsResponseDto value)? $default,){
final _that = this;
switch (_that) {
case _InstalledAppsResponseDto() 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, List<InstalledAppItemDto> apps, int createdAt, int? updatedAt)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _InstalledAppsResponseDto() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.apps,_that.createdAt,_that.updatedAt);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, List<InstalledAppItemDto> apps, int createdAt, int? updatedAt) $default,) {final _that = this;
switch (_that) {
case _InstalledAppsResponseDto():
return $default(_that.id,_that.deviceIdentificator,_that.apps,_that.createdAt,_that.updatedAt);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, List<InstalledAppItemDto> apps, int createdAt, int? updatedAt)? $default,) {final _that = this;
switch (_that) {
case _InstalledAppsResponseDto() when $default != null:
return $default(_that.id,_that.deviceIdentificator,_that.apps,_that.createdAt,_that.updatedAt);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _InstalledAppsResponseDto implements InstalledAppsResponseDto {
const _InstalledAppsResponseDto({required this.id, required this.deviceIdentificator, required final List<InstalledAppItemDto> apps, required this.createdAt, this.updatedAt}): _apps = apps;
factory _InstalledAppsResponseDto.fromJson(Map<String, dynamic> json) => _$InstalledAppsResponseDtoFromJson(json);
@override final String id;
@override final String deviceIdentificator;
final List<InstalledAppItemDto> _apps;
@override List<InstalledAppItemDto> get apps {
if (_apps is EqualUnmodifiableListView) return _apps;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_apps);
}
@override final int createdAt;
@override final int? updatedAt;
/// Create a copy of InstalledAppsResponseDto
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$InstalledAppsResponseDtoCopyWith<_InstalledAppsResponseDto> get copyWith => __$InstalledAppsResponseDtoCopyWithImpl<_InstalledAppsResponseDto>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$InstalledAppsResponseDtoToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _InstalledAppsResponseDto&&(identical(other.id, id) || other.id == id)&&(identical(other.deviceIdentificator, deviceIdentificator) || other.deviceIdentificator == deviceIdentificator)&&const DeepCollectionEquality().equals(other._apps, _apps)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,deviceIdentificator,const DeepCollectionEquality().hash(_apps),createdAt,updatedAt);
@override
String toString() {
return 'InstalledAppsResponseDto(id: $id, deviceIdentificator: $deviceIdentificator, apps: $apps, createdAt: $createdAt, updatedAt: $updatedAt)';
}
}
/// @nodoc
abstract mixin class _$InstalledAppsResponseDtoCopyWith<$Res> implements $InstalledAppsResponseDtoCopyWith<$Res> {
factory _$InstalledAppsResponseDtoCopyWith(_InstalledAppsResponseDto value, $Res Function(_InstalledAppsResponseDto) _then) = __$InstalledAppsResponseDtoCopyWithImpl;
@override @useResult
$Res call({
String id, String deviceIdentificator, List<InstalledAppItemDto> apps, int createdAt, int? updatedAt
});
}
/// @nodoc
class __$InstalledAppsResponseDtoCopyWithImpl<$Res>
implements _$InstalledAppsResponseDtoCopyWith<$Res> {
__$InstalledAppsResponseDtoCopyWithImpl(this._self, this._then);
final _InstalledAppsResponseDto _self;
final $Res Function(_InstalledAppsResponseDto) _then;
/// Create a copy of InstalledAppsResponseDto
/// 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? apps = null,Object? createdAt = null,Object? updatedAt = freezed,}) {
return _then(_InstalledAppsResponseDto(
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,apps: null == apps ? _self._apps : apps // ignore: cast_nullable_to_non_nullable
as List<InstalledAppItemDto>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
as int,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// @nodoc
mixin _$InstalledAppItemDto {
String get appUid; String get appName; bool get isEnabled;
/// Create a copy of InstalledAppItemDto
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$InstalledAppItemDtoCopyWith<InstalledAppItemDto> get copyWith => _$InstalledAppItemDtoCopyWithImpl<InstalledAppItemDto>(this as InstalledAppItemDto, _$identity);
/// Serializes this InstalledAppItemDto to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is InstalledAppItemDto&&(identical(other.appUid, appUid) || other.appUid == appUid)&&(identical(other.appName, appName) || other.appName == appName)&&(identical(other.isEnabled, isEnabled) || other.isEnabled == isEnabled));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,appUid,appName,isEnabled);
@override
String toString() {
return 'InstalledAppItemDto(appUid: $appUid, appName: $appName, isEnabled: $isEnabled)';
}
}
/// @nodoc
abstract mixin class $InstalledAppItemDtoCopyWith<$Res> {
factory $InstalledAppItemDtoCopyWith(InstalledAppItemDto value, $Res Function(InstalledAppItemDto) _then) = _$InstalledAppItemDtoCopyWithImpl;
@useResult
$Res call({
String appUid, String appName, bool isEnabled
});
}
/// @nodoc
class _$InstalledAppItemDtoCopyWithImpl<$Res>
implements $InstalledAppItemDtoCopyWith<$Res> {
_$InstalledAppItemDtoCopyWithImpl(this._self, this._then);
final InstalledAppItemDto _self;
final $Res Function(InstalledAppItemDto) _then;
/// Create a copy of InstalledAppItemDto
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? appUid = null,Object? appName = null,Object? isEnabled = null,}) {
return _then(_self.copyWith(
appUid: null == appUid ? _self.appUid : appUid // ignore: cast_nullable_to_non_nullable
as String,appName: null == appName ? _self.appName : appName // ignore: cast_nullable_to_non_nullable
as String,isEnabled: null == isEnabled ? _self.isEnabled : isEnabled // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [InstalledAppItemDto].
extension InstalledAppItemDtoPatterns on InstalledAppItemDto {
/// 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( _InstalledAppItemDto value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _InstalledAppItemDto() 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( _InstalledAppItemDto value) $default,){
final _that = this;
switch (_that) {
case _InstalledAppItemDto():
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( _InstalledAppItemDto value)? $default,){
final _that = this;
switch (_that) {
case _InstalledAppItemDto() 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 appUid, String appName, bool isEnabled)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _InstalledAppItemDto() when $default != null:
return $default(_that.appUid,_that.appName,_that.isEnabled);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 appUid, String appName, bool isEnabled) $default,) {final _that = this;
switch (_that) {
case _InstalledAppItemDto():
return $default(_that.appUid,_that.appName,_that.isEnabled);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 appUid, String appName, bool isEnabled)? $default,) {final _that = this;
switch (_that) {
case _InstalledAppItemDto() when $default != null:
return $default(_that.appUid,_that.appName,_that.isEnabled);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _InstalledAppItemDto implements InstalledAppItemDto {
const _InstalledAppItemDto({required this.appUid, required this.appName, required this.isEnabled});
factory _InstalledAppItemDto.fromJson(Map<String, dynamic> json) => _$InstalledAppItemDtoFromJson(json);
@override final String appUid;
@override final String appName;
@override final bool isEnabled;
/// Create a copy of InstalledAppItemDto
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$InstalledAppItemDtoCopyWith<_InstalledAppItemDto> get copyWith => __$InstalledAppItemDtoCopyWithImpl<_InstalledAppItemDto>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$InstalledAppItemDtoToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _InstalledAppItemDto&&(identical(other.appUid, appUid) || other.appUid == appUid)&&(identical(other.appName, appName) || other.appName == appName)&&(identical(other.isEnabled, isEnabled) || other.isEnabled == isEnabled));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,appUid,appName,isEnabled);
@override
String toString() {
return 'InstalledAppItemDto(appUid: $appUid, appName: $appName, isEnabled: $isEnabled)';
}
}
/// @nodoc
abstract mixin class _$InstalledAppItemDtoCopyWith<$Res> implements $InstalledAppItemDtoCopyWith<$Res> {
factory _$InstalledAppItemDtoCopyWith(_InstalledAppItemDto value, $Res Function(_InstalledAppItemDto) _then) = __$InstalledAppItemDtoCopyWithImpl;
@override @useResult
$Res call({
String appUid, String appName, bool isEnabled
});
}
/// @nodoc
class __$InstalledAppItemDtoCopyWithImpl<$Res>
implements _$InstalledAppItemDtoCopyWith<$Res> {
__$InstalledAppItemDtoCopyWithImpl(this._self, this._then);
final _InstalledAppItemDto _self;
final $Res Function(_InstalledAppItemDto) _then;
/// Create a copy of InstalledAppItemDto
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? appUid = null,Object? appName = null,Object? isEnabled = null,}) {
return _then(_InstalledAppItemDto(
appUid: null == appUid ? _self.appUid : appUid // ignore: cast_nullable_to_non_nullable
as String,appName: null == appName ? _self.appName : appName // ignore: cast_nullable_to_non_nullable
as String,isEnabled: null == isEnabled ? _self.isEnabled : isEnabled // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -0,0 +1,44 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'installed_apps_response_dto.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_InstalledAppsResponseDto _$InstalledAppsResponseDtoFromJson(
Map<String, dynamic> json,
) => _InstalledAppsResponseDto(
id: json['id'] as String,
deviceIdentificator: json['deviceIdentificator'] as String,
apps: (json['apps'] as List<dynamic>)
.map((e) => InstalledAppItemDto.fromJson(e as Map<String, dynamic>))
.toList(),
createdAt: (json['createdAt'] as num).toInt(),
updatedAt: (json['updatedAt'] as num?)?.toInt(),
);
Map<String, dynamic> _$InstalledAppsResponseDtoToJson(
_InstalledAppsResponseDto instance,
) => <String, dynamic>{
'id': instance.id,
'deviceIdentificator': instance.deviceIdentificator,
'apps': instance.apps,
'createdAt': instance.createdAt,
'updatedAt': instance.updatedAt,
};
_InstalledAppItemDto _$InstalledAppItemDtoFromJson(Map<String, dynamic> json) =>
_InstalledAppItemDto(
appUid: json['appUid'] as String,
appName: json['appName'] as String,
isEnabled: json['isEnabled'] as bool,
);
Map<String, dynamic> _$InstalledAppItemDtoToJson(
_InstalledAppItemDto instance,
) => <String, dynamic>{
'appUid': instance.appUid,
'appName': instance.appName,
'isEnabled': instance.isEnabled,
};

View File

@@ -0,0 +1,42 @@
import 'package:device_management/src/core/data/datasources/installed_apps_remote_datasource.dart';
import 'package:device_management/src/core/data/models/installed_apps_response_dto.dart';
import 'package:device_management/src/core/domain/entities/installed_app_entity.dart';
import 'package:device_management/src/core/domain/repositories/installed_apps_repository.dart';
import 'package:dio/dio.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
class InstalledAppsRepositoryImpl implements InstalledAppsRepository {
InstalledAppsRepositoryImpl(this._remote);
final InstalledAppsRemoteDatasource _remote;
@override
Future<List<InstalledAppEntity>> getInstalledApps({
required String identificator,
}) async {
try {
final response = await _remote.getInstalledApps(
identificator: identificator,
);
return response.toEntities();
} on ApiException catch (e) {
if (e.statusCode == 404) return [];
rethrow;
} on DioException catch (e) {
if (e.response?.statusCode == 404) return [];
throw mapDioError(e, defaultMessage: 'Error getting installed apps');
}
}
@override
Future<List<InstalledAppEntity>> updateInstalledApps({
required String identificator,
required List<Map<String, dynamic>> apps,
}) async {
final response = await _remote.updateInstalledApps(
identificator: identificator,
apps: apps,
);
return response.toEntities();
}
}

View File

@@ -0,0 +1,12 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'installed_app_entity.freezed.dart';
@freezed
abstract class InstalledAppEntity with _$InstalledAppEntity {
const factory InstalledAppEntity({
required String appUid,
required String appName,
required bool isEnabled,
}) = _InstalledAppEntity;
}

View File

@@ -0,0 +1,277 @@
// 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 'installed_app_entity.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$InstalledAppEntity {
String get appUid; String get appName; bool get isEnabled;
/// Create a copy of InstalledAppEntity
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$InstalledAppEntityCopyWith<InstalledAppEntity> get copyWith => _$InstalledAppEntityCopyWithImpl<InstalledAppEntity>(this as InstalledAppEntity, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is InstalledAppEntity&&(identical(other.appUid, appUid) || other.appUid == appUid)&&(identical(other.appName, appName) || other.appName == appName)&&(identical(other.isEnabled, isEnabled) || other.isEnabled == isEnabled));
}
@override
int get hashCode => Object.hash(runtimeType,appUid,appName,isEnabled);
@override
String toString() {
return 'InstalledAppEntity(appUid: $appUid, appName: $appName, isEnabled: $isEnabled)';
}
}
/// @nodoc
abstract mixin class $InstalledAppEntityCopyWith<$Res> {
factory $InstalledAppEntityCopyWith(InstalledAppEntity value, $Res Function(InstalledAppEntity) _then) = _$InstalledAppEntityCopyWithImpl;
@useResult
$Res call({
String appUid, String appName, bool isEnabled
});
}
/// @nodoc
class _$InstalledAppEntityCopyWithImpl<$Res>
implements $InstalledAppEntityCopyWith<$Res> {
_$InstalledAppEntityCopyWithImpl(this._self, this._then);
final InstalledAppEntity _self;
final $Res Function(InstalledAppEntity) _then;
/// Create a copy of InstalledAppEntity
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? appUid = null,Object? appName = null,Object? isEnabled = null,}) {
return _then(_self.copyWith(
appUid: null == appUid ? _self.appUid : appUid // ignore: cast_nullable_to_non_nullable
as String,appName: null == appName ? _self.appName : appName // ignore: cast_nullable_to_non_nullable
as String,isEnabled: null == isEnabled ? _self.isEnabled : isEnabled // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [InstalledAppEntity].
extension InstalledAppEntityPatterns on InstalledAppEntity {
/// 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( _InstalledAppEntity value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _InstalledAppEntity() 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( _InstalledAppEntity value) $default,){
final _that = this;
switch (_that) {
case _InstalledAppEntity():
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( _InstalledAppEntity value)? $default,){
final _that = this;
switch (_that) {
case _InstalledAppEntity() 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 appUid, String appName, bool isEnabled)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _InstalledAppEntity() when $default != null:
return $default(_that.appUid,_that.appName,_that.isEnabled);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 appUid, String appName, bool isEnabled) $default,) {final _that = this;
switch (_that) {
case _InstalledAppEntity():
return $default(_that.appUid,_that.appName,_that.isEnabled);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 appUid, String appName, bool isEnabled)? $default,) {final _that = this;
switch (_that) {
case _InstalledAppEntity() when $default != null:
return $default(_that.appUid,_that.appName,_that.isEnabled);case _:
return null;
}
}
}
/// @nodoc
class _InstalledAppEntity implements InstalledAppEntity {
const _InstalledAppEntity({required this.appUid, required this.appName, required this.isEnabled});
@override final String appUid;
@override final String appName;
@override final bool isEnabled;
/// Create a copy of InstalledAppEntity
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$InstalledAppEntityCopyWith<_InstalledAppEntity> get copyWith => __$InstalledAppEntityCopyWithImpl<_InstalledAppEntity>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _InstalledAppEntity&&(identical(other.appUid, appUid) || other.appUid == appUid)&&(identical(other.appName, appName) || other.appName == appName)&&(identical(other.isEnabled, isEnabled) || other.isEnabled == isEnabled));
}
@override
int get hashCode => Object.hash(runtimeType,appUid,appName,isEnabled);
@override
String toString() {
return 'InstalledAppEntity(appUid: $appUid, appName: $appName, isEnabled: $isEnabled)';
}
}
/// @nodoc
abstract mixin class _$InstalledAppEntityCopyWith<$Res> implements $InstalledAppEntityCopyWith<$Res> {
factory _$InstalledAppEntityCopyWith(_InstalledAppEntity value, $Res Function(_InstalledAppEntity) _then) = __$InstalledAppEntityCopyWithImpl;
@override @useResult
$Res call({
String appUid, String appName, bool isEnabled
});
}
/// @nodoc
class __$InstalledAppEntityCopyWithImpl<$Res>
implements _$InstalledAppEntityCopyWith<$Res> {
__$InstalledAppEntityCopyWithImpl(this._self, this._then);
final _InstalledAppEntity _self;
final $Res Function(_InstalledAppEntity) _then;
/// Create a copy of InstalledAppEntity
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? appUid = null,Object? appName = null,Object? isEnabled = null,}) {
return _then(_InstalledAppEntity(
appUid: null == appUid ? _self.appUid : appUid // ignore: cast_nullable_to_non_nullable
as String,appName: null == appName ? _self.appName : appName // ignore: cast_nullable_to_non_nullable
as String,isEnabled: null == isEnabled ? _self.isEnabled : isEnabled // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -0,0 +1,12 @@
import 'package:device_management/src/core/domain/entities/installed_app_entity.dart';
abstract class InstalledAppsRepository {
Future<List<InstalledAppEntity>> getInstalledApps({
required String identificator,
});
Future<List<InstalledAppEntity>> updateInstalledApps({
required String identificator,
required List<Map<String, dynamic>> apps,
});
}

View File

@@ -0,0 +1,10 @@
import 'package:device_management/src/core/data/datasources/installed_apps_remote_datasource_impl.dart';
import 'package:device_management/src/core/data/repositories/installed_apps_repository_impl.dart';
import 'package:device_management/src/core/domain/repositories/installed_apps_repository.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
final installedAppsRepositoryProvider = Provider<InstalledAppsRepository>((ref) {
final remote = InstalledAppsRemoteDatasourceImpl(getIt<SaveFamilyRepository>());
return InstalledAppsRepositoryImpl(remote);
});

View File

@@ -0,0 +1,14 @@
import 'package:device_management/src/features/installed_apps/presentation/installed_apps_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class InstalledAppsBuilder {
const InstalledAppsBuilder();
Page<void> buildPage(BuildContext context, GoRouterState state) {
return MaterialPage<void>(
key: state.pageKey,
child: const InstalledAppsScreen(),
);
}
}

View File

@@ -0,0 +1,177 @@
import 'package:design_system/design_system.dart';
import 'package:device_management/src/core/domain/entities/installed_app_entity.dart';
import 'package:device_management/src/features/installed_apps/presentation/providers/installed_apps_controller.dart';
import 'package:device_management/src/features/installed_apps/presentation/providers/installed_apps_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_device_state/legacy_device_state.dart';
import 'package:legacy_theme/legacy_theme.dart';
import 'package:legacy_ui/legacy_ui.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:get_it/get_it.dart';
import 'package:utils/utils.dart';
class InstalledAppsScreen extends ConsumerWidget {
const InstalledAppsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final device = ref.watch(selectedDeviceProvider).value;
final primaryColor = context.sfColors.legacyPrimary;
ref.listen(installedAppsControllerProvider, (prev, next) {
next.showErrorOn(context);
if (prev != null && prev.isLoading && !next.isLoading && !next.hasError) {
showSuccessDialog(context, I18n.installedAppsUpdated);
}
});
if (device == null) {
return LegacyPageLayout(
title: context.translate(I18n.installedAppsTitle),
body: const LegacyLoadingIndicator(),
);
}
final appsAsync = ref.watch(installedAppsProvider(device.identificator));
final isUpdating = ref.watch(
installedAppsControllerProvider.select((s) => s.isLoading),
);
return LegacyPageLayout(
title: context.translate(I18n.installedAppsTitle),
showEdit: true,
onEditChange: () {
GetIt.I<NavigationContract>().pushTo(AppRoutes.appUsageSchedules);
},
body: appsAsync.when(
loading: () => const LegacyLoadingIndicator(),
error: (_, __) =>
Center(child: Text(context.translate(I18n.errorGeneric))),
data: (apps) {
if (apps.isEmpty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.apps_outlined,
color: primaryColor,
size: SizeUtils.getByScreen(small: 80, big: 90),
),
SizedBox(height: SizeUtils.getByScreen(small: 16, big: 20)),
Text(
context.translate(I18n.installedAppsEmpty),
style: const TextStyle(color: Colors.grey, fontSize: 16),
textAlign: TextAlign.center,
),
],
),
);
}
return AbsorbPointer(
absorbing: isUpdating,
child: Opacity(
opacity: isUpdating ? 0.5 : 1.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 24, big: 22),
vertical: SizeUtils.getByScreen(small: 8, big: 6),
),
child: Text(
context.translate(I18n.installedAppsSubtitle),
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 13, big: 14),
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
Expanded(
child: ListView.separated(
padding: SizeUtils.getByScreen(
small: const EdgeInsets.symmetric(
horizontal: 22,
vertical: 10,
),
big: const EdgeInsets.symmetric(
horizontal: 21,
vertical: 8,
),
),
itemCount: apps.length,
separatorBuilder: (_, __) => SizedBox(
height: SizeUtils.getByScreen(small: 10, big: 8),
),
itemBuilder: (_, index) => _AppCard(
app: apps[index],
onToggle: (enabled) async {
if (!await guardDeviceCommand(context, ref)) return;
if (!context.mounted) return;
ref
.read(installedAppsControllerProvider.notifier)
.toggleApp(
identificator: device.identificator,
appUid: apps[index].appUid,
isEnabled: enabled,
);
},
),
),
),
],
),
),
);
},
),
);
}
}
class _AppCard extends StatelessWidget {
final InstalledAppEntity app;
final ValueChanged<bool> onToggle;
const _AppCard({required this.app, required this.onToggle});
@override
Widget build(BuildContext context) {
final primaryColor = context.sfColors.legacyPrimary;
return Container(
padding: EdgeInsets.symmetric(
horizontal: SizeUtils.getByScreen(small: 16, big: 14),
vertical: SizeUtils.getByScreen(small: 12, big: 10),
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: const BorderRadius.all(Radius.circular(14)),
),
child: Row(
children: [
Expanded(
child: Text(
app.appName,
style: TextStyle(
fontSize: SizeUtils.getByScreen(small: 15, big: 16),
fontWeight: FontWeight.w500,
),
overflow: TextOverflow.ellipsis,
),
),
Switch.adaptive(
value: app.isEnabled,
onChanged: onToggle,
activeTrackColor: primaryColor,
),
],
),
);
}
}

View File

@@ -0,0 +1,30 @@
import 'dart:async';
import 'package:device_management/src/core/providers/installed_apps_repository_provider.dart';
import 'package:device_management/src/features/installed_apps/presentation/providers/installed_apps_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'installed_apps_controller.g.dart';
@Riverpod(keepAlive: true)
class InstalledAppsController extends _$InstalledAppsController {
@override
FutureOr<void> build() {}
Future<void> toggleApp({
required String identificator,
required String appUid,
required bool isEnabled,
}) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await ref.read(installedAppsRepositoryProvider).updateInstalledApps(
identificator: identificator,
apps: [
{'appUid': appUid, 'isEnabled': isEnabled},
],
);
ref.invalidate(installedAppsProvider(identificator));
});
}
}

View File

@@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'installed_apps_controller.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(InstalledAppsController)
const installedAppsControllerProvider = InstalledAppsControllerProvider._();
final class InstalledAppsControllerProvider
extends $AsyncNotifierProvider<InstalledAppsController, void> {
const InstalledAppsControllerProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'installedAppsControllerProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$installedAppsControllerHash();
@$internal
@override
InstalledAppsController create() => InstalledAppsController();
}
String _$installedAppsControllerHash() =>
r'00a01c2dea9e0acaca4f4b7212033381b59b1f7e';
abstract class _$InstalledAppsController extends $AsyncNotifier<void> {
FutureOr<void> build();
@$mustCallSuper
@override
void runBuild() {
build();
final ref = this.ref as $Ref<AsyncValue<void>, void>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<void>, void>,
AsyncValue<void>,
Object?,
Object?
>;
element.handleValue(ref, null);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:device_management/src/core/domain/entities/installed_app_entity.dart';
import 'package:device_management/src/core/providers/installed_apps_repository_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'installed_apps_provider.g.dart';
@riverpod
Future<List<InstalledAppEntity>> installedApps(
Ref ref,
String identificator,
) async {
return ref
.read(installedAppsRepositoryProvider)
.getInstalledApps(identificator: identificator);
}

View File

@@ -0,0 +1,87 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'installed_apps_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(installedApps)
const installedAppsProvider = InstalledAppsFamily._();
final class InstalledAppsProvider
extends
$FunctionalProvider<
AsyncValue<List<InstalledAppEntity>>,
List<InstalledAppEntity>,
FutureOr<List<InstalledAppEntity>>
>
with
$FutureModifier<List<InstalledAppEntity>>,
$FutureProvider<List<InstalledAppEntity>> {
const InstalledAppsProvider._({
required InstalledAppsFamily super.from,
required String super.argument,
}) : super(
retry: null,
name: r'installedAppsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$installedAppsHash();
@override
String toString() {
return r'installedAppsProvider'
''
'($argument)';
}
@$internal
@override
$FutureProviderElement<List<InstalledAppEntity>> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<InstalledAppEntity>> create(Ref ref) {
final argument = this.argument as String;
return installedApps(ref, argument);
}
@override
bool operator ==(Object other) {
return other is InstalledAppsProvider && other.argument == argument;
}
@override
int get hashCode {
return argument.hashCode;
}
}
String _$installedAppsHash() => r'4f79eec7a30099f6d01f7a22d1e921818a544a26';
final class InstalledAppsFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<List<InstalledAppEntity>>, String> {
const InstalledAppsFamily._()
: super(
retry: null,
name: r'installedAppsProvider',
dependencies: null,
$allTransitiveDependencies: null,
isAutoDispose: true,
);
InstalledAppsProvider call(String identificator) =>
InstalledAppsProvider._(argument: identificator, from: this);
@override
String toString() => r'installedAppsProvider';
}