Большой рефакторинг
Из domain выкинуты типы vault, теперь он ничего не знает о Yandex. Объявления провайдеров вынесены в vault-api, а реализации в data
This commit is contained in:
@@ -79,4 +79,5 @@ dependencies {
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
|
||||
implementation(project(":domain"))
|
||||
implementation(project(":vault-api"))
|
||||
}
|
||||
@@ -7,29 +7,38 @@ import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IFile
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
||||
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
|
||||
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
|
||||
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
|
||||
import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase
|
||||
import com.github.nullptroma.wallenc.domain.usecases.ManageStoragesEncryptionUseCase
|
||||
import com.github.nullptroma.wallenc.domain.usecases.ManageVaultUseCase
|
||||
import com.github.nullptroma.wallenc.domain.usecases.RemoveStorageUseCase
|
||||
import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase
|
||||
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
|
||||
import com.github.nullptroma.wallenc.presentation.ViewModelBase
|
||||
import com.github.nullptroma.wallenc.presentation.extensions.toPrintable
|
||||
import com.github.nullptroma.wallenc.vaultapi.described
|
||||
import com.github.nullptroma.wallenc.vaultapi.locals
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
class LocalVaultViewModel @Inject constructor(
|
||||
private val manageLocalVaultUseCase: ManageLocalVaultUseCase,
|
||||
private val vaultsManager: IVaultsManager,
|
||||
private val manageVaultUseCase: ManageVaultUseCase,
|
||||
private val removeStorageUseCase: RemoveStorageUseCase,
|
||||
private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
|
||||
private val storageFileManagementUseCase: StorageFileManagementUseCase,
|
||||
@@ -38,6 +47,13 @@ class LocalVaultViewModel @Inject constructor(
|
||||
private val taskOrchestrator: ITaskOrchestrator,
|
||||
private val logger: ILogger
|
||||
) : ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf(), true)) {
|
||||
|
||||
private val localVaultUuid: UUID?
|
||||
get() = vaultsManager.vaults.value.described().locals.firstOrNull()?.uuid
|
||||
|
||||
private val localStoragesFlow = vaultsManager.vaults
|
||||
.map { vaults -> vaults.described().locals.firstOrNull() }
|
||||
.flatMapLatest { v -> v?.storages ?: flowOf(emptyList()) }
|
||||
private val _messages = MutableSharedFlow<String>()
|
||||
val messages: SharedFlow<String> = _messages
|
||||
|
||||
@@ -69,24 +85,21 @@ class LocalVaultViewModel @Inject constructor(
|
||||
|
||||
private fun collectFlows() {
|
||||
viewModelScope.launch {
|
||||
manageLocalVaultUseCase.localStorages.combine(getOpenedStoragesUseCase.openedStorages) { local, opened ->
|
||||
if (local == null) {
|
||||
return@combine Pair(true, emptyList<Tree<IStorageInfo>>())
|
||||
}
|
||||
localStoragesFlow.combine(getOpenedStoragesUseCase.openedStorages) { local, opened ->
|
||||
val list = mutableListOf<Tree<IStorageInfo>>()
|
||||
for (storage in local) {
|
||||
var tree = Tree(storage)
|
||||
var tree = Tree<IStorageInfo>(storage)
|
||||
list.add(tree)
|
||||
while (opened.containsKey(tree.value.uuid)) {
|
||||
val child = opened.getValue(tree.value.uuid)
|
||||
val nextTree = Tree(child)
|
||||
val nextTree = Tree<IStorageInfo>(child)
|
||||
tree.children = listOf(nextTree)
|
||||
tree = nextTree
|
||||
}
|
||||
}
|
||||
return@combine Pair(false, list)
|
||||
}.collect { (loading, trees) ->
|
||||
isLoading = loading
|
||||
list
|
||||
}.collect { trees ->
|
||||
isLoading = false
|
||||
updateState(state.value.copy(storagesList = trees))
|
||||
}
|
||||
}
|
||||
@@ -127,7 +140,9 @@ class LocalVaultViewModel @Inject constructor(
|
||||
dispatcher = Dispatchers.IO,
|
||||
work = { ctx ->
|
||||
ctx.log(TaskLogLevel.Info, "Creating storage…")
|
||||
manageLocalVaultUseCase.createStorage()
|
||||
val uuid = localVaultUuid
|
||||
?: throw IllegalStateException("Local vault is not registered")
|
||||
manageVaultUseCase.createStorage(uuid)
|
||||
ctx.log(TaskLogLevel.Info, "Storage created")
|
||||
},
|
||||
)
|
||||
|
||||
@@ -44,9 +44,9 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.github.nullptroma.wallenc.domain.auth.RemoteYandexAuthResult
|
||||
import com.github.nullptroma.wallenc.domain.enums.VaultType
|
||||
import com.github.nullptroma.wallenc.presentation.R
|
||||
import com.github.nullptroma.wallenc.vaultapi.CloudBrand
|
||||
import com.github.nullptroma.wallenc.vaultapi.VaultLinkOutcome
|
||||
|
||||
@Composable
|
||||
fun RemoteVaultsScreen(
|
||||
@@ -114,10 +114,9 @@ fun RemoteVaultsScreen(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = when (item.type) {
|
||||
VaultType.YANDEX ->
|
||||
text = when (item.brand) {
|
||||
CloudBrand.YANDEX ->
|
||||
stringResource(R.string.remote_vault_type_yandex)
|
||||
else -> item.type.name
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -172,14 +171,14 @@ fun RemoteVaultsScreen(
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
viewModel.setAddChoiceVisible(false)
|
||||
viewModel.yandexSignIn.launch { outcome ->
|
||||
viewModel.remoteAuthenticator.beginLink(CloudBrand.YANDEX) { outcome ->
|
||||
when (outcome) {
|
||||
is RemoteYandexAuthResult.Success ->
|
||||
viewModel.onYandexAuthSuccess(outcome.accessToken)
|
||||
is RemoteYandexAuthResult.Failure ->
|
||||
is VaultLinkOutcome.Success ->
|
||||
viewModel.onLinkSucceeded(outcome.registration)
|
||||
is VaultLinkOutcome.Failed ->
|
||||
Toast.makeText(context, outcome.message, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
RemoteYandexAuthResult.Cancelled -> { }
|
||||
VaultLinkOutcome.Cancelled -> { }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes
|
||||
|
||||
import com.github.nullptroma.wallenc.domain.enums.VaultType
|
||||
import com.github.nullptroma.wallenc.vaultapi.CloudBrand
|
||||
import java.util.UUID
|
||||
|
||||
data class RemoteVaultListItem(
|
||||
val uuid: UUID,
|
||||
val type: VaultType,
|
||||
val brand: CloudBrand,
|
||||
val label: String,
|
||||
)
|
||||
|
||||
@@ -13,6 +13,5 @@ data class RemoteVaultsScreenState(
|
||||
val vaults: List<RemoteVaultListItem> = emptyList(),
|
||||
val isBusy: Boolean = false,
|
||||
val addChoiceVisible: Boolean = false,
|
||||
/** Карточка, для которой показан диалог удаления */
|
||||
val vaultPendingDelete: RemoteVaultListItem? = null,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.nullptroma.wallenc.domain.auth.RemoteYandexSignInLauncher
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IYandexVault
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
|
||||
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
|
||||
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
|
||||
import com.github.nullptroma.wallenc.presentation.ViewModelBase
|
||||
import com.github.nullptroma.wallenc.vaultapi.RemoteVaultAuthenticator
|
||||
import com.github.nullptroma.wallenc.vaultapi.VaultDescriptor
|
||||
import com.github.nullptroma.wallenc.vaultapi.VaultRegistrar
|
||||
import com.github.nullptroma.wallenc.vaultapi.VaultRegistration
|
||||
import com.github.nullptroma.wallenc.vaultapi.described
|
||||
import com.github.nullptroma.wallenc.vaultapi.remotes
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -18,24 +22,22 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
class RemoteVaultsViewModel @Inject constructor(
|
||||
private val vaultsManager: IVaultsManager,
|
||||
val yandexSignIn: RemoteYandexSignInLauncher,
|
||||
private val vaultRegistrar: VaultRegistrar,
|
||||
val remoteAuthenticator: RemoteVaultAuthenticator,
|
||||
private val taskOrchestrator: ITaskOrchestrator,
|
||||
) : ViewModelBase<RemoteVaultsScreenState>(RemoteVaultsScreenState()) {
|
||||
|
||||
val uiState = combine(
|
||||
vaultsManager.remoteVaults,
|
||||
vaultsManager.vaults,
|
||||
state,
|
||||
) { remotes, base ->
|
||||
) { all, base ->
|
||||
base.copy(
|
||||
vaults = remotes.map { v ->
|
||||
val label = when (v) {
|
||||
is IYandexVault -> v.accountEmail
|
||||
else -> v.uuid.toString()
|
||||
}
|
||||
vaults = all.described().remotes.mapNotNull { v ->
|
||||
val descriptor = v.descriptor as? VaultDescriptor.LinkedRemote ?: return@mapNotNull null
|
||||
RemoteVaultListItem(
|
||||
uuid = v.uuid,
|
||||
type = v.type,
|
||||
label = label,
|
||||
uuid = descriptor.uuid,
|
||||
brand = descriptor.brand,
|
||||
label = descriptor.accountDisplayName,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -53,15 +55,15 @@ class RemoteVaultsViewModel @Inject constructor(
|
||||
updateState(state.value.copy(isBusy = busy))
|
||||
}
|
||||
|
||||
fun onYandexAuthSuccess(accessToken: String) {
|
||||
fun onLinkSucceeded(registration: VaultRegistration) {
|
||||
setBusy(true)
|
||||
taskOrchestrator.enqueue(
|
||||
title = "Add Yandex vault",
|
||||
title = "Add remote vault",
|
||||
dispatcher = Dispatchers.IO,
|
||||
work = { ctx ->
|
||||
try {
|
||||
ctx.log(TaskLogLevel.Info, "Adding vault…")
|
||||
vaultsManager.addYandexVault(accessToken)
|
||||
vaultRegistrar.register(registration)
|
||||
ctx.log(TaskLogLevel.Info, "Vault added")
|
||||
} catch (e: Exception) {
|
||||
ctx.log(TaskLogLevel.Error, e.message ?: "Failed to add vault")
|
||||
@@ -93,7 +95,7 @@ class RemoteVaultsViewModel @Inject constructor(
|
||||
work = { ctx ->
|
||||
try {
|
||||
ctx.log(TaskLogLevel.Info, "Removing remote vault…")
|
||||
vaultsManager.removeRemoteVault(uuid)
|
||||
vaultRegistrar.unregister(uuid)
|
||||
ctx.log(TaskLogLevel.Info, "Remote vault removed")
|
||||
} catch (e: Exception) {
|
||||
ctx.log(TaskLogLevel.Error, e.message ?: "Failed to remove vault")
|
||||
|
||||
Reference in New Issue
Block a user