mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-23 17:26:35 +00:00
refactor: Replace ApiDevicesCubit with DevicesBloc
This commit is contained in:
parent
3a525f0d11
commit
710b9b53dd
|
@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart';
|
||||||
|
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
|
@ -32,7 +32,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
late final BackupsBloc backupsBloc;
|
late final BackupsBloc backupsBloc;
|
||||||
late final DnsRecordsCubit dnsRecordsCubit;
|
late final DnsRecordsCubit dnsRecordsCubit;
|
||||||
late final RecoveryKeyBloc recoveryKeyBloc;
|
late final RecoveryKeyBloc recoveryKeyBloc;
|
||||||
late final ApiDevicesCubit apiDevicesCubit;
|
late final DevicesBloc devicesBloc;
|
||||||
late final ServerJobsBloc serverJobsBloc;
|
late final ServerJobsBloc serverJobsBloc;
|
||||||
late final ConnectionStatusBloc connectionStatusBloc;
|
late final ConnectionStatusBloc connectionStatusBloc;
|
||||||
late final ServerDetailsCubit serverDetailsCubit;
|
late final ServerDetailsCubit serverDetailsCubit;
|
||||||
|
@ -48,7 +48,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
backupsBloc = BackupsBloc();
|
backupsBloc = BackupsBloc();
|
||||||
dnsRecordsCubit = DnsRecordsCubit();
|
dnsRecordsCubit = DnsRecordsCubit();
|
||||||
recoveryKeyBloc = RecoveryKeyBloc();
|
recoveryKeyBloc = RecoveryKeyBloc();
|
||||||
apiDevicesCubit = ApiDevicesCubit();
|
devicesBloc = DevicesBloc();
|
||||||
serverJobsBloc = ServerJobsBloc();
|
serverJobsBloc = ServerJobsBloc();
|
||||||
connectionStatusBloc = ConnectionStatusBloc();
|
connectionStatusBloc = ConnectionStatusBloc();
|
||||||
serverDetailsCubit = ServerDetailsCubit();
|
serverDetailsCubit = ServerDetailsCubit();
|
||||||
|
@ -93,7 +93,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
||||||
create: (final _) => recoveryKeyBloc,
|
create: (final _) => recoveryKeyBloc,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => apiDevicesCubit,
|
create: (final _) => devicesBloc,
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (final _) => serverJobsBloc,
|
create: (final _) => serverJobsBloc,
|
||||||
|
|
114
lib/logic/bloc/devices/devices_bloc.dart
Normal file
114
lib/logic/bloc/devices/devices_bloc.dart
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
|
import 'package:selfprivacy/logic/api_maps/generic_result.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
|
|
||||||
|
part 'devices_event.dart';
|
||||||
|
part 'devices_state.dart';
|
||||||
|
|
||||||
|
class DevicesBloc extends Bloc<DevicesEvent, DevicesState> {
|
||||||
|
DevicesBloc() : super(DevicesInitial()) {
|
||||||
|
on<DevicesListChanged>(
|
||||||
|
_mapDevicesListChangedToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
on<DeleteDevice>(
|
||||||
|
_mapDeleteDeviceToState,
|
||||||
|
transformer: sequential(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final apiConnectionRepository = getIt<ApiConnectionRepository>();
|
||||||
|
_apiDataSubscription = apiConnectionRepository.dataStream.listen(
|
||||||
|
(final ApiData apiData) {
|
||||||
|
print('============');
|
||||||
|
print(apiData.devices.data);
|
||||||
|
add(
|
||||||
|
DevicesListChanged(apiData.devices.data),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _apiDataSubscription;
|
||||||
|
|
||||||
|
Future<void> _mapDevicesListChangedToState(
|
||||||
|
final DevicesListChanged event,
|
||||||
|
final Emitter<DevicesState> emit,
|
||||||
|
) async {
|
||||||
|
if (state is DevicesDeleting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
print(event.devices);
|
||||||
|
if (event.devices == null) {
|
||||||
|
emit(DevicesError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit(DevicesLoaded(devices: event.devices!));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.devices.invalidate();
|
||||||
|
await getIt<ApiConnectionRepository>().reload(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _mapDeleteDeviceToState(
|
||||||
|
final DeleteDevice event,
|
||||||
|
final Emitter<DevicesState> emit,
|
||||||
|
) async {
|
||||||
|
// Optimistically remove the device from the list
|
||||||
|
emit(
|
||||||
|
DevicesDeleting(
|
||||||
|
devices: state.devices
|
||||||
|
.where((final d) => d.name != event.device.name)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final GenericResult<void> response = await getIt<ApiConnectionRepository>()
|
||||||
|
.api
|
||||||
|
.deleteApiToken(event.device.name);
|
||||||
|
if (response.success) {
|
||||||
|
getIt<ApiConnectionRepository>().apiData.devices.invalidate();
|
||||||
|
emit(
|
||||||
|
DevicesLoaded(
|
||||||
|
devices: state.devices
|
||||||
|
.where((final d) => d.name != event.device.name)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
getIt<NavigationService>()
|
||||||
|
.showSnackBar(response.message ?? 'Error deleting device');
|
||||||
|
emit(DevicesLoaded(devices: state.devices));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getNewDeviceKey() async {
|
||||||
|
final GenericResult<String> response =
|
||||||
|
await getIt<ApiConnectionRepository>().api.createDeviceToken();
|
||||||
|
if (response.success) {
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
response.message ?? 'Error getting new device key',
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChange(final Change<DevicesState> change) {
|
||||||
|
super.onChange(change);
|
||||||
|
print(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_apiDataSubscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
23
lib/logic/bloc/devices/devices_event.dart
Normal file
23
lib/logic/bloc/devices/devices_event.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
part of 'devices_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DevicesEvent extends Equatable {
|
||||||
|
const DevicesEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesListChanged extends DevicesEvent {
|
||||||
|
const DevicesListChanged(this.devices);
|
||||||
|
|
||||||
|
final List<ApiToken>? devices;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteDevice extends DevicesEvent {
|
||||||
|
const DeleteDevice(this.device);
|
||||||
|
|
||||||
|
final ApiToken device;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [device];
|
||||||
|
}
|
53
lib/logic/bloc/devices/devices_state.dart
Normal file
53
lib/logic/bloc/devices/devices_state.dart
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
part of 'devices_bloc.dart';
|
||||||
|
|
||||||
|
sealed class DevicesState extends Equatable {
|
||||||
|
DevicesState({
|
||||||
|
required final List<ApiToken> devices,
|
||||||
|
}) : _hashCode = devices.hashCode;
|
||||||
|
|
||||||
|
final int _hashCode;
|
||||||
|
|
||||||
|
List<ApiToken> get _devices =>
|
||||||
|
getIt<ApiConnectionRepository>().apiData.devices.data ?? const [];
|
||||||
|
|
||||||
|
List<ApiToken> get devices => _devices;
|
||||||
|
ApiToken get thisDevice => _devices.firstWhere(
|
||||||
|
(final device) => device.isCaller,
|
||||||
|
orElse: () => ApiToken(
|
||||||
|
name: 'Error fetching device',
|
||||||
|
isCaller: true,
|
||||||
|
date: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ApiToken> get otherDevices =>
|
||||||
|
_devices.where((final device) => !device.isCaller).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesInitial extends DevicesState {
|
||||||
|
DevicesInitial() : super(devices: const []);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesLoaded extends DevicesState {
|
||||||
|
DevicesLoaded({required super.devices});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesError extends DevicesState {
|
||||||
|
DevicesError() : super(devices: const []);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DevicesDeleting extends DevicesState {
|
||||||
|
DevicesDeleting({required super.devices});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [_hashCode];
|
||||||
|
}
|
|
@ -16,14 +16,6 @@ class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
|
||||||
_mapRecoveryKeyStatusChangedToState,
|
_mapRecoveryKeyStatusChangedToState,
|
||||||
transformer: sequential(),
|
transformer: sequential(),
|
||||||
);
|
);
|
||||||
on<CreateNewRecoveryKey>(
|
|
||||||
_mapCreateNewRecoveryKeyToState,
|
|
||||||
transformer: sequential(),
|
|
||||||
);
|
|
||||||
on<ConsumedNewRecoveryKey>(
|
|
||||||
_mapRecoveryKeyStatusRefreshToState,
|
|
||||||
transformer: sequential(),
|
|
||||||
);
|
|
||||||
on<RecoveryKeyStatusRefresh>(
|
on<RecoveryKeyStatusRefresh>(
|
||||||
_mapRecoveryKeyStatusRefreshToState,
|
_mapRecoveryKeyStatusRefreshToState,
|
||||||
transformer: droppable(),
|
transformer: droppable(),
|
||||||
|
@ -45,9 +37,6 @@ class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
|
||||||
final RecoveryKeyStatusChanged event,
|
final RecoveryKeyStatusChanged event,
|
||||||
final Emitter<RecoveryKeyState> emit,
|
final Emitter<RecoveryKeyState> emit,
|
||||||
) async {
|
) async {
|
||||||
if (state is RecoveryKeyCreating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.recoveryKeyStatus == null) {
|
if (event.recoveryKeyStatus == null) {
|
||||||
emit(RecoveryKeyError());
|
emit(RecoveryKeyError());
|
||||||
return;
|
return;
|
||||||
|
@ -55,20 +44,20 @@ class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
|
||||||
emit(RecoveryKeyLoaded(keyStatus: event.recoveryKeyStatus));
|
emit(RecoveryKeyLoaded(keyStatus: event.recoveryKeyStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _mapCreateNewRecoveryKeyToState(
|
Future<String> generateRecoveryKey({
|
||||||
final CreateNewRecoveryKey event,
|
final DateTime? expirationDate,
|
||||||
final Emitter<RecoveryKeyState> emit,
|
final int? numberOfUses,
|
||||||
) async {
|
}) async {
|
||||||
emit(RecoveryKeyCreating());
|
|
||||||
final GenericResult<String> response =
|
final GenericResult<String> response =
|
||||||
await getIt<ApiConnectionRepository>().api.generateRecoveryToken(
|
await getIt<ApiConnectionRepository>()
|
||||||
event.expirationDate,
|
.api
|
||||||
event.numberOfUses,
|
.generateRecoveryToken(expirationDate, numberOfUses);
|
||||||
);
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
emit(RecoveryKeyCreating(recoveryKey: response.data));
|
getIt<ApiConnectionRepository>().apiData.recoveryKeyStatus.invalidate();
|
||||||
|
unawaited(getIt<ApiConnectionRepository>().reload(null));
|
||||||
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
emit(RecoveryKeyCreating(error: response.message ?? 'Unknown error'));
|
throw GenerationError(response.message ?? 'Unknown error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,3 +81,8 @@ class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GenerationError extends Error {
|
||||||
|
GenerationError(this.message);
|
||||||
|
final String message;
|
||||||
|
}
|
||||||
|
|
|
@ -13,26 +13,6 @@ class RecoveryKeyStatusChanged extends RecoveryKeyEvent {
|
||||||
List<Object?> get props => [recoveryKeyStatus];
|
List<Object?> get props => [recoveryKeyStatus];
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateNewRecoveryKey extends RecoveryKeyEvent {
|
|
||||||
const CreateNewRecoveryKey({
|
|
||||||
this.expirationDate,
|
|
||||||
this.numberOfUses,
|
|
||||||
});
|
|
||||||
|
|
||||||
final DateTime? expirationDate;
|
|
||||||
final int? numberOfUses;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [expirationDate, numberOfUses];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConsumedNewRecoveryKey extends RecoveryKeyEvent {
|
|
||||||
const ConsumedNewRecoveryKey();
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [];
|
|
||||||
}
|
|
||||||
|
|
||||||
class RecoveryKeyStatusRefresh extends RecoveryKeyEvent {
|
class RecoveryKeyStatusRefresh extends RecoveryKeyEvent {
|
||||||
const RecoveryKeyStatusRefresh();
|
const RecoveryKeyStatusRefresh();
|
||||||
|
|
||||||
|
|
|
@ -54,14 +54,3 @@ class RecoveryKeyError extends RecoveryKeyState {
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [_hashCode];
|
List<Object> get props => [_hashCode];
|
||||||
}
|
}
|
||||||
|
|
||||||
class RecoveryKeyCreating extends RecoveryKeyState {
|
|
||||||
RecoveryKeyCreating({this.recoveryKey, this.error})
|
|
||||||
: super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false));
|
|
||||||
|
|
||||||
final String? recoveryKey;
|
|
||||||
final String? error;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [_hashCode, recoveryKey, error];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart';
|
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
|
||||||
|
|
||||||
part 'devices_state.dart';
|
|
||||||
|
|
||||||
class ApiDevicesCubit extends ServerConnectionDependentCubit<ApiDevicesState> {
|
|
||||||
ApiDevicesCubit() : super(const ApiDevicesState.initial());
|
|
||||||
|
|
||||||
final ServerApi api = ServerApi();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void load() async {
|
|
||||||
// if (serverInstallationCubit.state is ServerInstallationFinished) {
|
|
||||||
_refetch();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
emit(ApiDevicesState([state.thisDevice], LoadingStatus.refreshing));
|
|
||||||
_refetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _refetch() async {
|
|
||||||
final List<ApiToken>? devices = await _getApiTokens();
|
|
||||||
if (devices != null) {
|
|
||||||
emit(ApiDevicesState(devices, LoadingStatus.success));
|
|
||||||
} else {
|
|
||||||
emit(const ApiDevicesState([], LoadingStatus.error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<ApiToken>?> _getApiTokens() async {
|
|
||||||
final GenericResult<List<ApiToken>> response = await api.getApiTokens();
|
|
||||||
if (response.success) {
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteDevice(final ApiToken device) async {
|
|
||||||
final GenericResult<void> response = await api.deleteApiToken(device.name);
|
|
||||||
if (response.success) {
|
|
||||||
emit(
|
|
||||||
ApiDevicesState(
|
|
||||||
state.devices.where((final d) => d.name != device.name).toList(),
|
|
||||||
LoadingStatus.success,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>()
|
|
||||||
.showSnackBar(response.message ?? 'Error deleting device');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> getNewDeviceKey() async {
|
|
||||||
final GenericResult<String> response = await api.createDeviceToken();
|
|
||||||
if (response.success) {
|
|
||||||
return response.data;
|
|
||||||
} else {
|
|
||||||
getIt<NavigationService>().showSnackBar(
|
|
||||||
response.message ?? 'Error getting new device key',
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clear() {
|
|
||||||
emit(const ApiDevicesState.initial());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
part of 'devices_cubit.dart';
|
|
||||||
|
|
||||||
class ApiDevicesState extends ServerInstallationDependendState {
|
|
||||||
const ApiDevicesState(this._devices, this.status);
|
|
||||||
|
|
||||||
const ApiDevicesState.initial() : this(const [], LoadingStatus.uninitialized);
|
|
||||||
final List<ApiToken> _devices;
|
|
||||||
final LoadingStatus status;
|
|
||||||
|
|
||||||
List<ApiToken> get devices => _devices;
|
|
||||||
ApiToken get thisDevice => _devices.firstWhere(
|
|
||||||
(final device) => device.isCaller,
|
|
||||||
orElse: () => ApiToken(
|
|
||||||
name: 'Error fetching device',
|
|
||||||
isCaller: true,
|
|
||||||
date: DateTime.now(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
List<ApiToken> get otherDevices =>
|
|
||||||
_devices.where((final device) => !device.isCaller).toList();
|
|
||||||
|
|
||||||
ApiDevicesState copyWith({
|
|
||||||
final List<ApiToken>? devices,
|
|
||||||
final LoadingStatus? status,
|
|
||||||
}) =>
|
|
||||||
ApiDevicesState(
|
|
||||||
devices ?? _devices,
|
|
||||||
status ?? this.status,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [_devices];
|
|
||||||
}
|
|
|
@ -41,7 +41,7 @@ class UsersCubit extends ServerConnectionDependentCubit<UsersState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
if (getIt<ApiConnectionRepository>().connectionStatus !=
|
if (getIt<ApiConnectionRepository>().connectionStatus ==
|
||||||
ConnectionStatus.nonexistent) {
|
ConnectionStatus.nonexistent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.da
|
||||||
import 'package:selfprivacy/logic/models/backup.dart';
|
import 'package:selfprivacy/logic/models/backup.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
import 'package:selfprivacy/logic/models/hive/server_details.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
import 'package:selfprivacy/logic/models/json/server_job.dart';
|
||||||
|
@ -104,6 +105,7 @@ class ApiConnectionRepository {
|
||||||
_apiData.volumes.data = await _apiData.volumes.fetchData();
|
_apiData.volumes.data = await _apiData.volumes.fetchData();
|
||||||
_apiData.recoveryKeyStatus.data =
|
_apiData.recoveryKeyStatus.data =
|
||||||
await _apiData.recoveryKeyStatus.fetchData();
|
await _apiData.recoveryKeyStatus.fetchData();
|
||||||
|
_apiData.devices.data = await _apiData.devices.fetchData();
|
||||||
_dataStream.add(_apiData);
|
_dataStream.add(_apiData);
|
||||||
|
|
||||||
connectionStatus = ConnectionStatus.connected;
|
connectionStatus = ConnectionStatus.connected;
|
||||||
|
@ -145,6 +147,8 @@ class ApiConnectionRepository {
|
||||||
.refetchData(version, () => _dataStream.add(_apiData));
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
await _apiData.recoveryKeyStatus
|
await _apiData.recoveryKeyStatus
|
||||||
.refetchData(version, () => _dataStream.add(_apiData));
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
|
await _apiData.devices
|
||||||
|
.refetchData(version, () => _dataStream.add(_apiData));
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitData() {
|
void emitData() {
|
||||||
|
@ -181,6 +185,9 @@ class ApiData {
|
||||||
recoveryKeyStatus = ApiDataElement<RecoveryKeyStatus>(
|
recoveryKeyStatus = ApiDataElement<RecoveryKeyStatus>(
|
||||||
fetchData: () async => (await api.getRecoveryTokenStatus()).data,
|
fetchData: () async => (await api.getRecoveryTokenStatus()).data,
|
||||||
ttl: 300,
|
ttl: 300,
|
||||||
|
),
|
||||||
|
devices = ApiDataElement<List<ApiToken>>(
|
||||||
|
fetchData: () async => (await api.getApiTokens()).data,
|
||||||
);
|
);
|
||||||
|
|
||||||
ApiDataElement<List<ServerJob>> serverJobs;
|
ApiDataElement<List<ServerJob>> serverJobs;
|
||||||
|
@ -190,6 +197,7 @@ class ApiData {
|
||||||
ApiDataElement<List<Service>> services;
|
ApiDataElement<List<Service>> services;
|
||||||
ApiDataElement<List<ServerDiskVolume>> volumes;
|
ApiDataElement<List<ServerDiskVolume>> volumes;
|
||||||
ApiDataElement<RecoveryKeyStatus> recoveryKeyStatus;
|
ApiDataElement<RecoveryKeyStatus> recoveryKeyStatus;
|
||||||
|
ApiDataElement<List<ApiToken>> devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConnectionStatus {
|
enum ConnectionStatus {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
|
||||||
|
|
||||||
part 'api_token.g.dart';
|
part 'api_token.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ApiToken {
|
class ApiToken extends Equatable {
|
||||||
factory ApiToken.fromJson(final Map<String, dynamic> json) =>
|
factory ApiToken.fromJson(final Map<String, dynamic> json) =>
|
||||||
_$ApiTokenFromJson(json);
|
_$ApiTokenFromJson(json);
|
||||||
ApiToken({
|
const ApiToken({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.isCaller,
|
required this.isCaller,
|
||||||
|
@ -25,4 +26,7 @@ class ApiToken {
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
@JsonKey(name: 'is_caller')
|
@JsonKey(name: 'is_caller')
|
||||||
final bool isCaller;
|
final bool isCaller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [name, date, isCaller];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
import 'package:selfprivacy/logic/models/json/api_token.dart';
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
|
@ -22,12 +21,11 @@ class DevicesScreen extends StatefulWidget {
|
||||||
class _DevicesScreenState extends State<DevicesScreen> {
|
class _DevicesScreenState extends State<DevicesScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) {
|
||||||
final ApiDevicesState devicesStatus =
|
final DevicesState devicesStatus = context.watch<DevicesBloc>().state;
|
||||||
context.watch<ApiDevicesCubit>().state;
|
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await context.read<ApiDevicesCubit>().refresh();
|
await context.read<DevicesBloc>().refresh();
|
||||||
},
|
},
|
||||||
child: BrandHeroScreen(
|
child: BrandHeroScreen(
|
||||||
heroTitle: 'devices.main_screen.header'.tr(),
|
heroTitle: 'devices.main_screen.header'.tr(),
|
||||||
|
@ -35,13 +33,13 @@ class _DevicesScreenState extends State<DevicesScreen> {
|
||||||
hasBackButton: true,
|
hasBackButton: true,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
children: [
|
children: [
|
||||||
if (devicesStatus.status == LoadingStatus.uninitialized) ...[
|
if (devicesStatus is DevicesInitial) ...[
|
||||||
const Center(
|
const Center(
|
||||||
heightFactor: 8,
|
heightFactor: 8,
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (devicesStatus.status != LoadingStatus.uninitialized) ...[
|
if (devicesStatus is! DevicesInitial) ...[
|
||||||
_DevicesInfo(
|
_DevicesInfo(
|
||||||
devicesStatus: devicesStatus,
|
devicesStatus: devicesStatus,
|
||||||
),
|
),
|
||||||
|
@ -70,7 +68,7 @@ class _DevicesInfo extends StatelessWidget {
|
||||||
required this.devicesStatus,
|
required this.devicesStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ApiDevicesState devicesStatus;
|
final DevicesState devicesStatus;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Column(
|
Widget build(final BuildContext context) => Column(
|
||||||
|
@ -82,7 +80,9 @@ class _DevicesInfo extends StatelessWidget {
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_DeviceTile(device: devicesStatus.thisDevice),
|
_DeviceTile(
|
||||||
|
device: devicesStatus.thisDevice,
|
||||||
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
|
@ -91,14 +91,18 @@ class _DevicesInfo extends StatelessWidget {
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (devicesStatus.status == LoadingStatus.refreshing) ...[
|
if (devicesStatus is DevicesDeleting) ...[
|
||||||
const Center(
|
const Center(
|
||||||
heightFactor: 4,
|
heightFactor: 4,
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
...devicesStatus.otherDevices
|
if (devicesStatus is! DevicesDeleting)
|
||||||
.map((final device) => _DeviceTile(device: device)),
|
...devicesStatus.otherDevices.map(
|
||||||
|
(final device) => _DeviceTile(
|
||||||
|
device: device,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -110,7 +114,7 @@ class _DeviceTile extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => ListTile(
|
Widget build(final BuildContext context) => ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
|
contentPadding: EdgeInsets.zero,
|
||||||
title: Text(device.name),
|
title: Text(device.name),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'devices.main_screen.access_granted_on'
|
'devices.main_screen.access_granted_on'
|
||||||
|
@ -161,7 +165,7 @@ class _DeviceTile extends StatelessWidget {
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text('devices.revoke_device_alert.yes'.tr()),
|
child: Text('devices.revoke_device_alert.yes'.tr()),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<ApiDevicesCubit>().deleteDevice(device);
|
context.read<DevicesBloc>().add(DeleteDevice(device));
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:cubit_form/cubit_form.dart';
|
import 'package:cubit_form/cubit_form.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart';
|
import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
@ -17,7 +17,7 @@ class NewDeviceScreen extends StatelessWidget {
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
children: [
|
children: [
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: context.read<ApiDevicesCubit>().getNewDeviceKey(),
|
future: context.read<DevicesBloc>().getNewDeviceKey(),
|
||||||
builder: (
|
builder: (
|
||||||
final BuildContext context,
|
final BuildContext context,
|
||||||
final AsyncSnapshot<Object?> snapshot,
|
final AsyncSnapshot<Object?> snapshot,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
||||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
|
@ -41,7 +42,6 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
break;
|
break;
|
||||||
case RecoveryKeyInitial():
|
case RecoveryKeyInitial():
|
||||||
case RecoveryKeyError():
|
case RecoveryKeyError():
|
||||||
case RecoveryKeyCreating():
|
|
||||||
subtitle = 'recovery_key.key_connection_error'.tr();
|
subtitle = 'recovery_key.key_connection_error'.tr();
|
||||||
widgets = [
|
widgets = [
|
||||||
const Icon(Icons.sentiment_dissatisfied_outlined),
|
const Icon(Icons.sentiment_dissatisfied_outlined),
|
||||||
|
@ -234,12 +234,13 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
});
|
});
|
||||||
context.read<RecoveryKeyBloc>().add(
|
try {
|
||||||
CreateNewRecoveryKey(
|
final String token =
|
||||||
|
await context.read<RecoveryKeyBloc>().generateRecoveryKey(
|
||||||
|
numberOfUses: _isAmountToggled
|
||||||
|
? int.tryParse(_amountController.text)
|
||||||
|
: null,
|
||||||
expirationDate: _isExpirationToggled ? _selectedDate : null,
|
expirationDate: _isExpirationToggled ? _selectedDate : null,
|
||||||
numberOfUses:
|
|
||||||
_isAmountToggled ? int.tryParse(_amountController.text) : null,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
|
@ -249,9 +250,18 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
||||||
});
|
});
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
materialRoute(
|
materialRoute(
|
||||||
const RecoveryKeyReceiving(),
|
RecoveryKeyReceiving(recoveryKey: token),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} on GenerationError catch (e) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
getIt<NavigationService>().showSnackBar(
|
||||||
|
'recovery_key.generation_error'.tr(args: [e.message]),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateErrorStatuses() {
|
void _updateErrorStatuses() {
|
||||||
|
|
|
@ -1,36 +1,23 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart';
|
|
||||||
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
|
||||||
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
|
|
||||||
class RecoveryKeyReceiving extends StatelessWidget {
|
class RecoveryKeyReceiving extends StatelessWidget {
|
||||||
const RecoveryKeyReceiving({super.key});
|
const RecoveryKeyReceiving({required this.recoveryKey, super.key});
|
||||||
|
|
||||||
|
final String recoveryKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
Widget build(final BuildContext context) => BrandHeroScreen(
|
||||||
final recoveryKeyState = context.watch<RecoveryKeyBloc>().state;
|
|
||||||
|
|
||||||
final String? recoveryKey = recoveryKeyState is RecoveryKeyCreating
|
|
||||||
? recoveryKeyState.recoveryKey
|
|
||||||
: null;
|
|
||||||
|
|
||||||
final String? error =
|
|
||||||
recoveryKeyState is RecoveryKeyCreating ? recoveryKeyState.error : null;
|
|
||||||
|
|
||||||
return BrandHeroScreen(
|
|
||||||
heroTitle: 'recovery_key.key_main_header'.tr(),
|
heroTitle: 'recovery_key.key_main_header'.tr(),
|
||||||
heroSubtitle: 'recovery_key.key_receiving_description'.tr(),
|
heroSubtitle: 'recovery_key.key_receiving_description'.tr(),
|
||||||
hasBackButton: true,
|
hasBackButton: false,
|
||||||
hasFlashButton: false,
|
hasFlashButton: false,
|
||||||
children: [
|
children: [
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (recoveryKey == null && error == null)
|
|
||||||
const Center(child: CircularProgressIndicator()),
|
|
||||||
if (recoveryKey != null)
|
|
||||||
Text(
|
Text(
|
||||||
recoveryKey,
|
recoveryKey,
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
|
@ -39,16 +26,6 @@ class RecoveryKeyReceiving extends StatelessWidget {
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
if (error != null)
|
|
||||||
Text(
|
|
||||||
'recovery_key.generation_error'.tr(args: [error]),
|
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
|
||||||
fontSize: 24,
|
|
||||||
fontFamily: 'RobotoMono',
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
@ -59,11 +36,9 @@ class RecoveryKeyReceiving extends StatelessWidget {
|
||||||
BrandButton.filled(
|
BrandButton.filled(
|
||||||
child: Text('recovery_key.key_receiving_done'.tr()),
|
child: Text('recovery_key.key_receiving_done'.tr()),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<RecoveryKeyBloc>().add(const ConsumedNewRecoveryKey());
|
|
||||||
Navigator.of(context).popUntil((final route) => route.isFirst);
|
Navigator.of(context).popUntil((final route) => route.isFirst);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue