import 'dart:async'; import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.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/get_it/resources_model.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/providers/providers_controller.dart'; part 'volumes_event.dart'; part 'volumes_state.dart'; class VolumesBloc extends Bloc { VolumesBloc() : super(VolumesInitial()) { on( _loadState, transformer: droppable(), ); on( _resetState, transformer: droppable(), ); on( _updateState, transformer: droppable(), ); on( _resizeVolume, transformer: droppable(), ); final connectionRepository = getIt(); _apiStatusSubscription = connectionRepository.connectionStatusStream .listen((final ConnectionStatus connectionStatus) { switch (connectionStatus) { case ConnectionStatus.nonexistent: add(const VolumesServerReset()); isLoaded = false; break; case ConnectionStatus.connected: if (!isLoaded) { add(const VolumesServerLoaded()); isLoaded = true; } break; default: break; } }); _apiDataSubscription = connectionRepository.dataStream.listen( (final ApiData apiData) { if (apiData.volumes.data == null) { add(const VolumesServerReset()); } else { add( VolumesServerStateChanged( apiData.volumes.data!, ), ); } }, ); _resourcesModelSubscription = getIt().statusStream.listen((final event) { if (event is ChangedServerProviderCredentials) { add(const VolumesServerLoaded()); } }); } late StreamSubscription _apiStatusSubscription; late StreamSubscription _apiDataSubscription; late StreamSubscription _resourcesModelSubscription; bool isLoaded = false; Future getPricePerGb() async { if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) { return null; } Price? price; final location = state.location; if (location != null) { final pricingResult = await ProvidersController.currentServerProvider! .getAdditionalPricing(location); if (pricingResult.data == null || !pricingResult.success) { getIt().showSnackBar('server.pricing_error'.tr()); return price; } price = pricingResult.data!.perVolumeGb; return price; } else { await Future.delayed(Duration.zero); getIt().showSnackBar('server.pricing_error'.tr()); return price; } } Future _loadState( final VolumesServerLoaded event, final Emitter emit, ) async { if (getIt().currentConnectionStatus == ConnectionStatus.nonexistent) { return; } emit(VolumesLoading()); late final GenericResult>? volumesResult; if (ProvidersController.currentServerProvider?.isAuthorized ?? false) { volumesResult = await ProvidersController.currentServerProvider?.getVolumes(); } else { volumesResult = null; } final serverVolumes = getIt().apiData.volumes.data; if (serverVolumes == null && volumesResult != null && volumesResult.data.isNotEmpty) { emit(VolumesLoading(providerVolumes: volumesResult.data)); return; } else if (serverVolumes != null) { emit( VolumesLoaded( diskStatus: DiskStatus.fromVolumes( serverVolumes, volumesResult?.data ?? [], ), providerVolumes: volumesResult?.data ?? [], serverVolumesHashCode: Object.hashAll(serverVolumes), ), ); } } Future _resetState( final VolumesServerReset event, final Emitter emit, ) async { emit(VolumesInitial()); } @override void onChange(final Change change) { super.onChange(change); } @override Future close() async { await _apiStatusSubscription.cancel(); await _apiDataSubscription.cancel(); await _resourcesModelSubscription.cancel(); await super.close(); } Future invalidateCache() async { getIt().apiData.volumes.invalidate(); } Future _updateState( final VolumesServerStateChanged event, final Emitter emit, ) async { final serverVolumes = event.volumes; final providerVolumes = state.providerVolumes; if (state is VolumesLoading) { emit( VolumesLoaded( diskStatus: DiskStatus.fromVolumes( serverVolumes, providerVolumes, ), providerVolumes: providerVolumes, serverVolumesHashCode: Object.hashAll(serverVolumes), ), ); return; } emit( state.copyWith( diskStatus: DiskStatus.fromVolumes( serverVolumes, providerVolumes, ), providerVolumes: providerVolumes, serverVolumesHashCode: Object.hashAll(serverVolumes), ), ); } Future _resizeVolume( final VolumeResize event, final Emitter emit, ) async { if (state is! VolumesLoaded) { return; } if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) { return; } getIt().showSnackBar( 'storage.extending_volume_started'.tr(), ); emit( VolumesResizing( serverVolumesHashCode: state._serverVolumesHashCode, diskStatus: state.diskStatus, providerVolumes: state.providerVolumes, ), ); final resizedResult = await ProvidersController.currentServerProvider!.resizeVolume( event.volume.providerVolume!, event.newSize, ); if (!resizedResult.success || !resizedResult.data) { getIt().showSnackBar( 'storage.extending_volume_error'.tr(), ); emit( VolumesLoaded( serverVolumesHashCode: state._serverVolumesHashCode, diskStatus: state.diskStatus, providerVolumes: state.providerVolumes, ), ); return; } getIt().showSnackBar( 'storage.extending_volume_provider_waiting'.tr(), ); await Future.delayed(const Duration(seconds: 10)); await getIt().api.resizeVolume(event.volume.name); getIt().showSnackBar( 'storage.extending_volume_server_waiting'.tr(), ); await Future.delayed(const Duration(seconds: 20)); getIt().showSnackBar( 'storage.extending_volume_rebooting'.tr(), ); emit( VolumesLoaded( serverVolumesHashCode: state._serverVolumesHashCode, diskStatus: state.diskStatus, providerVolumes: state.providerVolumes, ), ); await getIt().api.reboot(); } }