Большой рефакторинг

Из domain выкинуты типы vault, теперь он ничего не знает о Yandex. Объявления провайдеров вынесены в vault-api, а реализации в data
This commit is contained in:
2026-04-27 02:47:02 +03:00
parent 2b1be58a8e
commit 1034e134c2
37 changed files with 527 additions and 219 deletions

View File

@@ -79,4 +79,5 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
implementation(project(":domain"))
implementation(project(":vault-api"))
}

View File

@@ -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")
},
)

View File

@@ -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 -> { }
}
}
},

View File

@@ -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,
)
)

View File

@@ -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")