refactor: Replace ApiDevicesCubit with DevicesBloc

pull/440/head
Inex Code 2024-02-09 14:07:03 +03:00
parent 3a525f0d11
commit 710b9b53dd
16 changed files with 297 additions and 252 deletions

View File

@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_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/devices/devices_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/services/services_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/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/server_detailed_info/server_detailed_info_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 DnsRecordsCubit dnsRecordsCubit;
late final RecoveryKeyBloc recoveryKeyBloc;
late final ApiDevicesCubit apiDevicesCubit;
late final DevicesBloc devicesBloc;
late final ServerJobsBloc serverJobsBloc;
late final ConnectionStatusBloc connectionStatusBloc;
late final ServerDetailsCubit serverDetailsCubit;
@ -48,7 +48,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
backupsBloc = BackupsBloc();
dnsRecordsCubit = DnsRecordsCubit();
recoveryKeyBloc = RecoveryKeyBloc();
apiDevicesCubit = ApiDevicesCubit();
devicesBloc = DevicesBloc();
serverJobsBloc = ServerJobsBloc();
connectionStatusBloc = ConnectionStatusBloc();
serverDetailsCubit = ServerDetailsCubit();
@ -93,7 +93,7 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
create: (final _) => recoveryKeyBloc,
),
BlocProvider(
create: (final _) => apiDevicesCubit,
create: (final _) => devicesBloc,
),
BlocProvider(
create: (final _) => serverJobsBloc,

View 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();
}
}

View 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];
}

View 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];
}

View File

@ -16,14 +16,6 @@ class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
_mapRecoveryKeyStatusChangedToState,
transformer: sequential(),
);
on<CreateNewRecoveryKey>(
_mapCreateNewRecoveryKeyToState,
transformer: sequential(),
);
on<ConsumedNewRecoveryKey>(
_mapRecoveryKeyStatusRefreshToState,
transformer: sequential(),
);
on<RecoveryKeyStatusRefresh>(
_mapRecoveryKeyStatusRefreshToState,
transformer: droppable(),
@ -45,9 +37,6 @@ class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
final RecoveryKeyStatusChanged event,
final Emitter<RecoveryKeyState> emit,
) async {
if (state is RecoveryKeyCreating) {
return;
}
if (event.recoveryKeyStatus == null) {
emit(RecoveryKeyError());
return;
@ -55,20 +44,20 @@ class RecoveryKeyBloc extends Bloc<RecoveryKeyEvent, RecoveryKeyState> {
emit(RecoveryKeyLoaded(keyStatus: event.recoveryKeyStatus));
}
Future<void> _mapCreateNewRecoveryKeyToState(
final CreateNewRecoveryKey event,
final Emitter<RecoveryKeyState> emit,
) async {
emit(RecoveryKeyCreating());
Future<String> generateRecoveryKey({
final DateTime? expirationDate,
final int? numberOfUses,
}) async {
final GenericResult<String> response =
await getIt<ApiConnectionRepository>().api.generateRecoveryToken(
event.expirationDate,
event.numberOfUses,
);
await getIt<ApiConnectionRepository>()
.api
.generateRecoveryToken(expirationDate, numberOfUses);
if (response.success) {
emit(RecoveryKeyCreating(recoveryKey: response.data));
getIt<ApiConnectionRepository>().apiData.recoveryKeyStatus.invalidate();
unawaited(getIt<ApiConnectionRepository>().reload(null));
return response.data;
} 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();
}
}
class GenerationError extends Error {
GenerationError(this.message);
final String message;
}

View File

@ -13,26 +13,6 @@ class RecoveryKeyStatusChanged extends RecoveryKeyEvent {
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 {
const RecoveryKeyStatusRefresh();

View File

@ -54,14 +54,3 @@ class RecoveryKeyError extends RecoveryKeyState {
@override
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];
}

View File

@ -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());
}
}

View File

@ -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];
}

View File

@ -41,7 +41,7 @@ class UsersCubit extends ServerConnectionDependentCubit<UsersState> {
}
Future<void> refresh() async {
if (getIt<ApiConnectionRepository>().connectionStatus !=
if (getIt<ApiConnectionRepository>().connectionStatus ==
ConnectionStatus.nonexistent) {
return;
}

View File

@ -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/hive/server_details.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/server_disk_volume.dart';
import 'package:selfprivacy/logic/models/json/server_job.dart';
@ -104,6 +105,7 @@ class ApiConnectionRepository {
_apiData.volumes.data = await _apiData.volumes.fetchData();
_apiData.recoveryKeyStatus.data =
await _apiData.recoveryKeyStatus.fetchData();
_apiData.devices.data = await _apiData.devices.fetchData();
_dataStream.add(_apiData);
connectionStatus = ConnectionStatus.connected;
@ -145,6 +147,8 @@ class ApiConnectionRepository {
.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.recoveryKeyStatus
.refetchData(version, () => _dataStream.add(_apiData));
await _apiData.devices
.refetchData(version, () => _dataStream.add(_apiData));
}
void emitData() {
@ -181,6 +185,9 @@ class ApiData {
recoveryKeyStatus = ApiDataElement<RecoveryKeyStatus>(
fetchData: () async => (await api.getRecoveryTokenStatus()).data,
ttl: 300,
),
devices = ApiDataElement<List<ApiToken>>(
fetchData: () async => (await api.getApiTokens()).data,
);
ApiDataElement<List<ServerJob>> serverJobs;
@ -190,6 +197,7 @@ class ApiData {
ApiDataElement<List<Service>> services;
ApiDataElement<List<ServerDiskVolume>> volumes;
ApiDataElement<RecoveryKeyStatus> recoveryKeyStatus;
ApiDataElement<List<ApiToken>> devices;
}
enum ConnectionStatus {

View File

@ -1,13 +1,14 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
part 'api_token.g.dart';
@JsonSerializable()
class ApiToken {
class ApiToken extends Equatable {
factory ApiToken.fromJson(final Map<String, dynamic> json) =>
_$ApiTokenFromJson(json);
ApiToken({
const ApiToken({
required this.name,
required this.date,
required this.isCaller,
@ -25,4 +26,7 @@ class ApiToken {
final DateTime date;
@JsonKey(name: 'is_caller')
final bool isCaller;
@override
List<Object?> get props => [name, date, isCaller];
}

View File

@ -2,8 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.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/models/json/api_token.dart';
import 'package:selfprivacy/ui/components/info_box/info_box.dart';
@ -22,12 +21,11 @@ class DevicesScreen extends StatefulWidget {
class _DevicesScreenState extends State<DevicesScreen> {
@override
Widget build(final BuildContext context) {
final ApiDevicesState devicesStatus =
context.watch<ApiDevicesCubit>().state;
final DevicesState devicesStatus = context.watch<DevicesBloc>().state;
return RefreshIndicator(
onRefresh: () async {
await context.read<ApiDevicesCubit>().refresh();
await context.read<DevicesBloc>().refresh();
},
child: BrandHeroScreen(
heroTitle: 'devices.main_screen.header'.tr(),
@ -35,13 +33,13 @@ class _DevicesScreenState extends State<DevicesScreen> {
hasBackButton: true,
hasFlashButton: false,
children: [
if (devicesStatus.status == LoadingStatus.uninitialized) ...[
if (devicesStatus is DevicesInitial) ...[
const Center(
heightFactor: 8,
child: CircularProgressIndicator(),
),
],
if (devicesStatus.status != LoadingStatus.uninitialized) ...[
if (devicesStatus is! DevicesInitial) ...[
_DevicesInfo(
devicesStatus: devicesStatus,
),
@ -70,7 +68,7 @@ class _DevicesInfo extends StatelessWidget {
required this.devicesStatus,
});
final ApiDevicesState devicesStatus;
final DevicesState devicesStatus;
@override
Widget build(final BuildContext context) => Column(
@ -82,7 +80,9 @@ class _DevicesInfo extends StatelessWidget {
color: Theme.of(context).colorScheme.secondary,
),
),
_DeviceTile(device: devicesStatus.thisDevice),
_DeviceTile(
device: devicesStatus.thisDevice,
),
const Divider(height: 1),
const SizedBox(height: 16),
Text(
@ -91,14 +91,18 @@ class _DevicesInfo extends StatelessWidget {
color: Theme.of(context).colorScheme.secondary,
),
),
if (devicesStatus.status == LoadingStatus.refreshing) ...[
if (devicesStatus is DevicesDeleting) ...[
const Center(
heightFactor: 4,
child: CircularProgressIndicator(),
),
],
...devicesStatus.otherDevices
.map((final device) => _DeviceTile(device: device)),
if (devicesStatus is! DevicesDeleting)
...devicesStatus.otherDevices.map(
(final device) => _DeviceTile(
device: device,
),
),
],
);
}
@ -110,7 +114,7 @@ class _DeviceTile extends StatelessWidget {
@override
Widget build(final BuildContext context) => ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
contentPadding: EdgeInsets.zero,
title: Text(device.name),
subtitle: Text(
'devices.main_screen.access_granted_on'
@ -161,7 +165,7 @@ class _DeviceTile extends StatelessWidget {
TextButton(
child: Text('devices.revoke_device_alert.yes'.tr()),
onPressed: () {
context.read<ApiDevicesCubit>().deleteDevice(device);
context.read<DevicesBloc>().add(DeleteDevice(device));
Navigator.of(context).pop();
},
),

View File

@ -1,7 +1,7 @@
import 'package:cubit_form/cubit_form.dart';
import 'package:easy_localization/easy_localization.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/ui/components/buttons/brand_button.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
@ -17,7 +17,7 @@ class NewDeviceScreen extends StatelessWidget {
hasFlashButton: false,
children: [
FutureBuilder(
future: context.read<ApiDevicesCubit>().getNewDeviceKey(),
future: context.read<DevicesBloc>().getNewDeviceKey(),
builder: (
final BuildContext context,
final AsyncSnapshot<Object?> snapshot,

View File

@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/cubit/server_installation/server_installation_cubit.dart';
import 'package:selfprivacy/ui/components/buttons/brand_button.dart';
@ -41,7 +42,6 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
break;
case RecoveryKeyInitial():
case RecoveryKeyError():
case RecoveryKeyCreating():
subtitle = 'recovery_key.key_connection_error'.tr();
widgets = [
const Icon(Icons.sentiment_dissatisfied_outlined),
@ -234,24 +234,34 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
setState(() {
_isLoading = true;
});
context.read<RecoveryKeyBloc>().add(
CreateNewRecoveryKey(
expirationDate: _isExpirationToggled ? _selectedDate : null,
numberOfUses:
_isAmountToggled ? int.tryParse(_amountController.text) : null,
),
);
if (!mounted) {
try {
final String token =
await context.read<RecoveryKeyBloc>().generateRecoveryKey(
numberOfUses: _isAmountToggled
? int.tryParse(_amountController.text)
: null,
expirationDate: _isExpirationToggled ? _selectedDate : null,
);
if (!mounted) {
return;
}
setState(() {
_isLoading = false;
});
await Navigator.of(context).push(
materialRoute(
RecoveryKeyReceiving(recoveryKey: token),
),
);
} on GenerationError catch (e) {
setState(() {
_isLoading = false;
});
getIt<NavigationService>().showSnackBar(
'recovery_key.generation_error'.tr(args: [e.message]),
);
return;
}
setState(() {
_isLoading = false;
});
await Navigator.of(context).push(
materialRoute(
const RecoveryKeyReceiving(),
),
);
}
void _updateErrorStatuses() {

View File

@ -1,36 +1,23 @@
import 'package:easy_localization/easy_localization.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/info_box/info_box.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
class RecoveryKeyReceiving extends StatelessWidget {
const RecoveryKeyReceiving({super.key});
const RecoveryKeyReceiving({required this.recoveryKey, super.key});
final String recoveryKey;
@override
Widget build(final BuildContext context) {
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(),
heroSubtitle: 'recovery_key.key_receiving_description'.tr(),
hasBackButton: true,
hasFlashButton: false,
children: [
const Divider(),
const SizedBox(height: 16),
if (recoveryKey == null && error == null)
const Center(child: CircularProgressIndicator()),
if (recoveryKey != null)
Widget build(final BuildContext context) => BrandHeroScreen(
heroTitle: 'recovery_key.key_main_header'.tr(),
heroSubtitle: 'recovery_key.key_receiving_description'.tr(),
hasBackButton: false,
hasFlashButton: false,
children: [
const Divider(),
const SizedBox(height: 16),
Text(
recoveryKey,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
@ -39,31 +26,19 @@ class RecoveryKeyReceiving extends StatelessWidget {
),
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 Divider(),
const SizedBox(height: 16),
InfoBox(
text: 'recovery_key.key_receiving_info'.tr(),
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
InfoBox(
text: 'recovery_key.key_receiving_info'.tr(),
),
const SizedBox(height: 16),
BrandButton.filled(
child: Text('recovery_key.key_receiving_done'.tr()),
onPressed: () {
context.read<RecoveryKeyBloc>().add(const ConsumedNewRecoveryKey());
Navigator.of(context).popUntil((final route) => route.isFirst);
},
),
],
);
}
const SizedBox(height: 16),
BrandButton.filled(
child: Text('recovery_key.key_receiving_done'.tr()),
onPressed: () {
Navigator.of(context).popUntil((final route) => route.isFirst);
},
),
],
);
}