Общий VaultScreen

This commit is contained in:
2026-05-03 19:47:18 +03:00
parent 1034e134c2
commit 78aa776adc
14 changed files with 226 additions and 99 deletions

View File

@@ -27,12 +27,15 @@ import com.github.nullptroma.wallenc.presentation.R
import com.github.nullptroma.wallenc.presentation.navigation.NavBarItemData import com.github.nullptroma.wallenc.presentation.navigation.NavBarItemData
import com.github.nullptroma.wallenc.presentation.navigation.NavigationState import com.github.nullptroma.wallenc.presentation.navigation.NavigationState
import com.github.nullptroma.wallenc.presentation.navigation.rememberNavigationState import com.github.nullptroma.wallenc.presentation.navigation.rememberNavigationState
import com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault.LocalVaultRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault.LocalVaultScreen
import com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault.LocalVaultViewModel
import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsRoute import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsScreen import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsScreen
import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsViewModel import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsViewModel
import com.github.nullptroma.wallenc.presentation.screens.main.screens.vault.LocalVaultRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.vault.LocalVaultScreen
import com.github.nullptroma.wallenc.presentation.screens.main.screens.vault.LocalVaultViewModel
import com.github.nullptroma.wallenc.presentation.screens.main.screens.vault.RemoteVaultViewModel
import com.github.nullptroma.wallenc.presentation.screens.main.screens.vault.VaultBrowserRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.vault.VaultBrowserScreen
import com.github.nullptroma.wallenc.presentation.screens.shared.TextEditRoute import com.github.nullptroma.wallenc.presentation.screens.shared.TextEditRoute
import com.github.nullptroma.wallenc.presentation.screens.shared.TextEditScreen import com.github.nullptroma.wallenc.presentation.screens.shared.TextEditScreen
@@ -110,7 +113,24 @@ fun MainScreen(
}) { }) {
RemoteVaultsScreen( RemoteVaultsScreen(
modifier = Modifier.padding(innerPaddings), modifier = Modifier.padding(innerPaddings),
viewModel = remoteVaultsViewModel viewModel = remoteVaultsViewModel,
onOpenVault = { item ->
navState.push(VaultBrowserRoute(item.uuid.toString()))
},
)
}
composable<VaultBrowserRoute>(enterTransition = {
fadeIn(tween(200))
}, exitTransition = {
fadeOut(tween(200))
}) { entry ->
val remoteVaultViewModel: RemoteVaultViewModel = hiltViewModel(entry)
VaultBrowserScreen(
modifier = Modifier.padding(innerPaddings),
viewModel = remoteVaultViewModel,
openTextEdit = { text ->
navState.push(TextEditRoute(text))
},
) )
} }
composable<TextEditRoute> { composable<TextEditRoute> {

View File

@@ -5,8 +5,8 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
import androidx.lifecycle.viewmodel.compose.saveable import androidx.lifecycle.viewmodel.compose.saveable
import com.github.nullptroma.wallenc.presentation.screens.ScreenRoute import com.github.nullptroma.wallenc.presentation.screens.ScreenRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault.LocalVaultRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsRoute import com.github.nullptroma.wallenc.presentation.screens.main.screens.remotes.RemoteVaultsRoute
import com.github.nullptroma.wallenc.presentation.screens.main.screens.vault.LocalVaultRoute
import com.github.nullptroma.wallenc.presentation.ViewModelBase import com.github.nullptroma.wallenc.presentation.ViewModelBase
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel

View File

@@ -6,4 +6,4 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@Parcelize @Parcelize
class RemoteVaultsRoute: MainRoute() class RemoteVaultsRoute : MainRoute()

View File

@@ -34,6 +34,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -41,7 +42,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.runtime.getValue
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.presentation.R import com.github.nullptroma.wallenc.presentation.R
@@ -52,6 +52,7 @@ import com.github.nullptroma.wallenc.vaultapi.VaultLinkOutcome
fun RemoteVaultsScreen( fun RemoteVaultsScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: RemoteVaultsViewModel = hiltViewModel(), viewModel: RemoteVaultsViewModel = hiltViewModel(),
onOpenVault: (RemoteVaultListItem) -> Unit,
) { ) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val context = LocalContext.current val context = LocalContext.current
@@ -92,7 +93,13 @@ fun RemoteVaultsScreen(
) { ) {
items(uiState.vaults, key = { it.uuid }) { item -> items(uiState.vaults, key = { it.uuid }) { item ->
ElevatedCard( ElevatedCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.clickable(
enabled = !uiState.isBusy,
interactionSource = remember { MutableInteractionSource() },
indication = null,
) { onOpenVault(item) },
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 2.dp), elevation = CardDefaults.elevatedCardElevation(defaultElevation = 2.dp),
colors = CardDefaults.elevatedCardColors( colors = CardDefaults.elevatedCardColors(

View File

@@ -4,10 +4,10 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog

View File

@@ -2,7 +2,6 @@ package com.github.nullptroma.wallenc.presentation.screens.main.screens.tasks
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.tasks.PipelineWork
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View File

@@ -1,4 +1,4 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.vault
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
@@ -6,8 +6,8 @@ import com.github.nullptroma.wallenc.domain.datatypes.Tree
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.ILogger import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo 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.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
@@ -18,76 +18,66 @@ import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase
import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase
import com.github.nullptroma.wallenc.presentation.ViewModelBase import com.github.nullptroma.wallenc.presentation.ViewModelBase
import com.github.nullptroma.wallenc.presentation.extensions.toPrintable 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.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine 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 kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
import javax.inject.Inject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@OptIn(ExperimentalCoroutinesApi::class) /**
@HiltViewModel * Общая логика дерева storages для локального и удалённого vault (presentation).
class LocalVaultViewModel @Inject constructor( */
private val vaultsManager: IVaultsManager, abstract class AbstractVaultBrowserViewModel(
private val manageVaultUseCase: ManageVaultUseCase, storagesFlow: Flow<List<IStorage>>,
private val canAddStorage: Boolean,
private val resolveCreateVaultUuid: () -> UUID?,
private val removeStorageUseCase: RemoveStorageUseCase, private val removeStorageUseCase: RemoveStorageUseCase,
private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase, private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
private val storageFileManagementUseCase: StorageFileManagementUseCase, private val storageFileManagementUseCase: StorageFileManagementUseCase,
private val manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase, private val manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase,
private val renameStorageUseCase: RenameStorageUseCase, private val renameStorageUseCase: RenameStorageUseCase,
private val manageVaultUseCase: ManageVaultUseCase,
private val taskOrchestrator: ITaskOrchestrator, private val taskOrchestrator: ITaskOrchestrator,
private val logger: ILogger private val logger: ILogger,
) : ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf(), true)) { ) : ViewModelBase<VaultBrowserScreenState>(
VaultBrowserScreenState(storagesList = emptyList(), isLoading = true, canAddStorage = canAddStorage),
) {
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>() private val _messages = MutableSharedFlow<String>()
val messages: SharedFlow<String> = _messages val messages: SharedFlow<String> = _messages
private var _taskCount: Int = 0 private var taskCount: Int = 0
private var tasksCount
get() = _taskCount
set(value) { set(value) {
_taskCount = value field = value
updateStateLoading() updateStateLoading()
} }
private var _isLoading: Boolean = false private var storagesLoading: Boolean = false
private var isLoading
get() = _isLoading
set(value) { set(value) {
_isLoading = value field = value
updateStateLoading() updateStateLoading()
} }
init { init {
collectFlows() collectFlows(storagesFlow)
} }
private fun updateStateLoading() { private fun updateStateLoading() {
updateState(state.value.copy( updateState(
isLoading = this.isLoading || this.tasksCount > 0 state.value.copy(
)) isLoading = storagesLoading || taskCount > 0,
),
)
} }
private fun collectFlows() { private fun collectFlows(storagesFlow: Flow<List<IStorage>>) {
viewModelScope.launch { viewModelScope.launch {
localStoragesFlow.combine(getOpenedStoragesUseCase.openedStorages) { local, opened -> storagesFlow.combine(getOpenedStoragesUseCase.openedStorages) { storages, opened ->
val list = mutableListOf<Tree<IStorageInfo>>() val list = mutableListOf<Tree<IStorageInfo>>()
for (storage in local) { for (storage in storages) {
var tree = Tree<IStorageInfo>(storage) var tree = Tree<IStorageInfo>(storage)
list.add(tree) list.add(tree)
while (opened.containsKey(tree.value.uuid)) { while (opened.containsKey(tree.value.uuid)) {
@@ -99,7 +89,7 @@ class LocalVaultViewModel @Inject constructor(
} }
list list
}.collect { trees -> }.collect { trees ->
isLoading = false storagesLoading = false
updateState(state.value.copy(storagesList = trees)) updateState(state.value.copy(storagesList = trees))
} }
} }
@@ -135,13 +125,14 @@ class LocalVaultViewModel @Inject constructor(
} }
fun createStorage() { fun createStorage() {
if (!state.value.canAddStorage) return
taskOrchestrator.enqueue( taskOrchestrator.enqueue(
title = "Create storage", title = "Create storage",
dispatcher = Dispatchers.IO, dispatcher = Dispatchers.IO,
work = { ctx -> work = { ctx ->
ctx.log(TaskLogLevel.Info, "Creating storage…") ctx.log(TaskLogLevel.Info, "Creating storage…")
val uuid = localVaultUuid val uuid = resolveCreateVaultUuid()
?: throw IllegalStateException("Local vault is not registered") ?: throw IllegalStateException("Vault is not available")
manageVaultUseCase.createStorage(uuid) manageVaultUseCase.createStorage(uuid)
ctx.log(TaskLogLevel.Info, "Storage created") ctx.log(TaskLogLevel.Info, "Storage created")
}, },
@@ -156,7 +147,7 @@ class LocalVaultViewModel @Inject constructor(
synchronized(storageOpMutex) { synchronized(storageOpMutex) {
if (runningStorages.contains(id)) return if (runningStorages.contains(id)) return
runningStorages.add(id) runningStorages.add(id)
tasksCount++ taskCount++
} }
val key = EncryptKey(password) val key = EncryptKey(password)
taskOrchestrator.enqueue( taskOrchestrator.enqueue(
@@ -196,7 +187,7 @@ class LocalVaultViewModel @Inject constructor(
} finally { } finally {
synchronized(storageOpMutex) { synchronized(storageOpMutex) {
runningStorages.remove(id) runningStorages.remove(id)
tasksCount-- taskCount--
} }
} }
}, },
@@ -208,7 +199,7 @@ class LocalVaultViewModel @Inject constructor(
synchronized(storageOpMutex) { synchronized(storageOpMutex) {
if (runningStorages.contains(id)) return if (runningStorages.contains(id)) return
runningStorages.add(id) runningStorages.add(id)
tasksCount++ taskCount++
} }
val key = EncryptKey(password) val key = EncryptKey(password)
taskOrchestrator.enqueue( taskOrchestrator.enqueue(
@@ -225,7 +216,7 @@ class LocalVaultViewModel @Inject constructor(
} finally { } finally {
synchronized(storageOpMutex) { synchronized(storageOpMutex) {
runningStorages.remove(id) runningStorages.remove(id)
tasksCount-- taskCount--
} }
} }
}, },
@@ -312,4 +303,4 @@ class LocalVaultViewModel @Inject constructor(
val openedMap = getOpenedStoragesUseCase.openedStorages.value val openedMap = getOpenedStoragesUseCase.openedStorages.value
return openedMap.containsKey(storage.uuid) return openedMap.containsKey(storage.uuid)
} }
} }

View File

@@ -1,4 +1,4 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.vault
import com.github.nullptroma.wallenc.presentation.screens.main.MainRoute import com.github.nullptroma.wallenc.presentation.screens.main.MainRoute
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@@ -6,4 +6,4 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@Parcelize @Parcelize
class LocalVaultRoute: MainRoute() class LocalVaultRoute : MainRoute()

View File

@@ -0,0 +1,18 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.vault
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
@Composable
fun LocalVaultScreen(
modifier: Modifier = Modifier,
viewModel: LocalVaultViewModel = hiltViewModel(),
openTextEdit: (String) -> Unit,
) {
VaultBrowserScreen(
modifier = modifier,
viewModel = viewModel,
openTextEdit = openTextEdit,
)
}

View File

@@ -0,0 +1,47 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.vault
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
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.vaultapi.described
import com.github.nullptroma.wallenc.vaultapi.locals
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class LocalVaultViewModel @Inject constructor(
vaultsManager: IVaultsManager,
manageVaultUseCase: ManageVaultUseCase,
removeStorageUseCase: RemoveStorageUseCase,
getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
storageFileManagementUseCase: StorageFileManagementUseCase,
manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase,
renameStorageUseCase: RenameStorageUseCase,
taskOrchestrator: ITaskOrchestrator,
logger: ILogger,
) : AbstractVaultBrowserViewModel(
storagesFlow = vaultsManager.vaults
.map { vaults -> vaults.described().locals.firstOrNull() }
.flatMapLatest { v -> v?.storages ?: flowOf(emptyList()) },
canAddStorage = true,
resolveCreateVaultUuid = { vaultsManager.vaults.value.described().locals.firstOrNull()?.uuid },
removeStorageUseCase = removeStorageUseCase,
getOpenedStoragesUseCase = getOpenedStoragesUseCase,
storageFileManagementUseCase = storageFileManagementUseCase,
manageStoragesEncryptionUseCase = manageStoragesEncryptionUseCase,
renameStorageUseCase = renameStorageUseCase,
manageVaultUseCase = manageVaultUseCase,
taskOrchestrator = taskOrchestrator,
logger = logger,
)

View File

@@ -0,0 +1,46 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.vault
import androidx.lifecycle.SavedStateHandle
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import java.util.UUID
import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class RemoteVaultViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
manageVaultUseCase: ManageVaultUseCase,
removeStorageUseCase: RemoveStorageUseCase,
getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
storageFileManagementUseCase: StorageFileManagementUseCase,
manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase,
renameStorageUseCase: RenameStorageUseCase,
taskOrchestrator: ITaskOrchestrator,
logger: ILogger,
) : AbstractVaultBrowserViewModel(
storagesFlow = manageVaultUseCase.storagesOf(savedStateHandle.requireVaultUuid()),
canAddStorage = false,
resolveCreateVaultUuid = { null },
removeStorageUseCase = removeStorageUseCase,
getOpenedStoragesUseCase = getOpenedStoragesUseCase,
storageFileManagementUseCase = storageFileManagementUseCase,
manageStoragesEncryptionUseCase = manageStoragesEncryptionUseCase,
renameStorageUseCase = renameStorageUseCase,
manageVaultUseCase = manageVaultUseCase,
taskOrchestrator = taskOrchestrator,
logger = logger,
)
private fun SavedStateHandle.requireVaultUuid(): UUID {
val raw = get<String>("vaultUuid") ?: error("Missing vault UUID in navigation arguments")
return UUID.fromString(raw)
}

View File

@@ -0,0 +1,9 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.vault
import com.github.nullptroma.wallenc.presentation.screens.ScreenRoute
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
@Serializable
@Parcelize
data class VaultBrowserRoute(val vaultUuid: String) : ScreenRoute()

View File

@@ -1,4 +1,4 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.vault
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -27,18 +27,16 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.github.nullptroma.wallenc.presentation.elements.StorageTree import com.github.nullptroma.wallenc.presentation.elements.StorageTree
import com.github.nullptroma.wallenc.presentation.extensions.gesturesDisabled import com.github.nullptroma.wallenc.presentation.extensions.gesturesDisabled
@Composable @Composable
fun LocalVaultScreen( fun VaultBrowserScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: LocalVaultViewModel = hiltViewModel(), viewModel: AbstractVaultBrowserViewModel,
openTextEdit: (String) -> Unit, openTextEdit: (String) -> Unit,
) { ) {
val uiState by viewModel.state.collectAsStateWithLifecycle() val uiState by viewModel.state.collectAsStateWithLifecycle()
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -52,55 +50,44 @@ fun LocalVaultScreen(
modifier = modifier, modifier = modifier,
contentWindowInsets = WindowInsets(0.dp), contentWindowInsets = WindowInsets(0.dp),
floatingActionButton = { floatingActionButton = {
FloatingActionButton( if (uiState.canAddStorage) {
onClick = { FloatingActionButton(
viewModel.createStorage() onClick = { viewModel.createStorage() },
}, ) {
Icon(Icons.Filled.Add, contentDescription = null)
}
}
},
) { innerPadding ->
LazyColumn(
modifier = Modifier
.padding(innerPadding)
.gesturesDisabled(uiState.isLoading),
) { ) {
Icon(Icons.Filled.Add, "Floating action button.")
}
}) { innerPadding ->
LazyColumn(modifier = Modifier.padding(innerPadding).gesturesDisabled(uiState.isLoading)) {
items(uiState.storagesList) { listItem -> items(uiState.storagesList) { listItem ->
StorageTree( StorageTree(
modifier = Modifier.padding(8.dp, 8.dp, 8.dp, 0.dp), modifier = Modifier.padding(8.dp, 8.dp, 8.dp, 0.dp),
tree = listItem, tree = listItem,
onClick = { onClick = { openTextEdit(it.value.uuid.toString()) },
openTextEdit(it.value.uuid.toString()) onRename = { tree, newName -> viewModel.rename(tree.value, newName) },
}, onRemove = { tree -> viewModel.remove(tree.value) },
onRename = { tree, newName ->
viewModel.rename(tree.value, newName)
},
onRemove = { tree ->
viewModel.remove(tree.value)
},
onEncrypt = { tree, password, encryptPath -> onEncrypt = { tree, password, encryptPath ->
viewModel.enableEncryption(tree.value, password, encryptPath) viewModel.enableEncryption(tree.value, password, encryptPath)
}, },
onOpenEncrypted = { tree, password, remember -> onOpenEncrypted = { tree, password, remember ->
viewModel.openEncryptedStorage(tree.value, password, remember) viewModel.openEncryptedStorage(tree.value, password, remember)
}, },
onCloseEncrypted = { tree -> onCloseEncrypted = { tree -> viewModel.closeEncryptedStorage(tree.value) },
viewModel.closeEncryptedStorage(tree.value) onDisableEncryption = { tree -> viewModel.disableEncryption(tree.value) },
}, getStatusText = { tree -> viewModel.getStorageStatus(tree.value) },
onDisableEncryption = { tree -> isEncryptionOpened = { tree -> viewModel.isEncryptionSessionOpen(tree.value) },
viewModel.disableEncryption(tree.value)
},
getStatusText = { tree ->
viewModel.getStorageStatus(tree.value)
},
isEncryptionOpened = { tree ->
viewModel.isEncryptionSessionOpen(tree.value)
},
) )
} }
item { item { Spacer(modifier = Modifier.height(8.dp)) }
Spacer(modifier = Modifier.height(8.dp))
}
} }
} }
if(uiState.isLoading) { if (uiState.isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Box(modifier = Modifier.fillMaxSize().alpha(0.6f).background(Color.Black)) Box(modifier = Modifier.fillMaxSize().alpha(0.6f).background(Color.Black))
CircularProgressIndicator( CircularProgressIndicator(
@@ -112,4 +99,3 @@ fun LocalVaultScreen(
} }
} }
} }

View File

@@ -1,6 +1,10 @@
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault package com.github.nullptroma.wallenc.presentation.screens.main.screens.vault
import com.github.nullptroma.wallenc.domain.datatypes.Tree import com.github.nullptroma.wallenc.domain.datatypes.Tree
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
data class LocalVaultScreenState(val storagesList: List<Tree<IStorageInfo>>, val isLoading: Boolean) data class VaultBrowserScreenState(
val storagesList: List<Tree<IStorageInfo>>,
val isLoading: Boolean,
val canAddStorage: Boolean = false,
)