From e1646611c2ac66eb377d3df1c4ccd7eb42a1957b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D1=8B=D1=82=D0=BA=D0=BE=D0=B2=20=D0=A0=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Sun, 9 Feb 2025 22:03:14 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BA=D0=BB=D0=B8=D0=BA=20=D1=81=D0=BA=D0=B2?= =?UTF-8?q?=D0=BE=D0=B7=D1=8C=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/storages/encrypt/EncryptedStorage.kt | 8 +- .../encrypt/EncryptedStorageAccessor.kt | 1 - .../wallenc/domain/interfaces/IVault.kt | 2 +- .../wallenc/domain/interfaces/IVaultInfo.kt | 2 +- .../usecases/ManageLocalVaultUseCase.kt | 2 +- .../presentation/extensions/Modifier.kt | 19 ++++ .../screens/local/vault/LocalVaultScreen.kt | 86 ++++++++++++------- .../local/vault/LocalVaultScreenState.kt | 2 +- .../local/vault/LocalVaultViewModel.kt | 44 ++++++++-- 9 files changed, 122 insertions(+), 44 deletions(-) diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorage.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorage.kt index e43e4fb..3d4f64a 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorage.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorage.kt @@ -46,7 +46,13 @@ class EncryptedStorage private constructor( override val isAvailable: StateFlow get() = source.isAvailable override val accessor: EncryptedStorageAccessor = - EncryptedStorageAccessor(source.accessor, encInfo.pathIv, key, "${uuid.toString().take(8)}$SYSTEM_HIDDEN_DIRNAME_POSTFIX", scope) + EncryptedStorageAccessor( + source = source.accessor, + pathIv = encInfo.pathIv, + key = key, + systemHiddenDirName = "${uuid.toString().take(8)}$SYSTEM_HIDDEN_DIRNAME_POSTFIX", + scope = scope + ) private suspend fun init() { checkKey() diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorageAccessor.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorageAccessor.kt index c656867..3ba8a79 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorageAccessor.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorageAccessor.kt @@ -51,7 +51,6 @@ class EncryptedStorageAccessor( private val dataEncryptor = Encryptor(key.toAesKey()) private val pathEncryptor: EncryptorWithStaticIv? = if(pathIv != null) EncryptorWithStaticIv(key.toAesKey(), pathIv) else null - private var systemHiddenFiles: List? = null private var systemHiddenFilesIsActual = false init { diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IVault.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IVault.kt index 48813ba..9ccbd83 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IVault.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IVault.kt @@ -4,7 +4,7 @@ import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo import kotlinx.coroutines.flow.StateFlow interface IVault : IVaultInfo { - override val storages: StateFlow> + override val storages: StateFlow?> suspend fun createStorage(): IStorage suspend fun createStorage(enc: StorageEncryptionInfo): IStorage diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IVaultInfo.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IVaultInfo.kt index ef2779b..03b4ee0 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IVaultInfo.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IVaultInfo.kt @@ -7,7 +7,7 @@ import java.util.UUID sealed interface IVaultInfo { val type: VaultType val uuid: UUID - val storages: StateFlow> + val storages: StateFlow?> val isAvailable: StateFlow val totalSpace: StateFlow val availableSpace: StateFlow diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageLocalVaultUseCase.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageLocalVaultUseCase.kt index 764235b..bd5b5b2 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageLocalVaultUseCase.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageLocalVaultUseCase.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map class ManageLocalVaultUseCase(private val manager: IVaultsManager, private val unlockManager: IUnlockManager) { - val localStorages: StateFlow> + val localStorages: StateFlow?> get() = manager.localVault.storages suspend fun createStorage() { diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/extensions/Modifier.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/extensions/Modifier.kt index bd67037..61345cb 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/extensions/Modifier.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/extensions/Modifier.kt @@ -8,6 +8,9 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.debugInspectorInfo import androidx.compose.ui.semantics.Role @@ -32,3 +35,19 @@ fun Modifier.ignoreVerticalParentPadding(vertical: Dp): Modifier { } } } + +fun Modifier.gesturesDisabled(disabled: Boolean = true) = + if (disabled) { + pointerInput(Unit) { + awaitPointerEventScope { + // we should wait for all new pointer events + while (true) { + awaitPointerEvent(pass = PointerEventPass.Initial) + .changes + .forEach(PointerInputChange::consume) + } + } + } + } else { + this + } \ No newline at end of file diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt index edff918..8f8d5df 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt @@ -1,26 +1,39 @@ package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault +import android.widget.ProgressBar +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.github.nullptroma.wallenc.presentation.elements.StorageTree +import com.github.nullptroma.wallenc.presentation.extensions.gesturesDisabled -@OptIn(ExperimentalMaterial3Api::class) @Composable fun LocalVaultScreen( modifier: Modifier = Modifier, @@ -29,36 +42,49 @@ fun LocalVaultScreen( ) { val uiState by viewModel.state.collectAsStateWithLifecycle() - Scaffold(modifier = modifier, contentWindowInsets = WindowInsets(0.dp), floatingActionButton = { - FloatingActionButton( - onClick = { - viewModel.createStorage() - }, - ) { - Icon(Icons.Filled.Add, "Floating action button.") - } - }) { innerPadding -> - LazyColumn(modifier = Modifier.padding(innerPadding)) { - items(uiState.storagesList) { listItem -> - StorageTree( - modifier = Modifier.padding(8.dp, 8.dp, 8.dp, 0.dp), - tree = listItem, - onClick = { - openTextEdit(it.value.uuid.toString()) - }, - onRename = { tree, newName -> - viewModel.rename(tree.value, newName) - }, - onRemove = { tree -> - viewModel.remove(tree.value) - }, - onEncrypt = { tree -> - viewModel.enableEncryptionAndOpenStorage(tree.value) - } - ) + Box { + Scaffold(modifier = modifier, contentWindowInsets = WindowInsets(0.dp), floatingActionButton = { + FloatingActionButton( + onClick = { + viewModel.createStorage() + }, + ) { + Icon(Icons.Filled.Add, "Floating action button.") } - item { - Spacer(modifier = Modifier.height(8.dp)) + }) { innerPadding -> + LazyColumn(modifier = Modifier.padding(innerPadding).gesturesDisabled(uiState.isLoading)) { + items(uiState.storagesList) { listItem -> + StorageTree( + modifier = Modifier.padding(8.dp, 8.dp, 8.dp, 0.dp), + tree = listItem, + onClick = { + openTextEdit(it.value.uuid.toString()) + }, + onRename = { tree, newName -> + viewModel.rename(tree.value, newName) + }, + onRemove = { tree -> + viewModel.remove(tree.value) + }, + onEncrypt = { tree -> + viewModel.enableEncryptionAndOpenStorage(tree.value) + } + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + } + } + } + + if(uiState.isLoading) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Box(modifier = Modifier.fillMaxSize().alpha(0.6f).background(Color.Black)) + CircularProgressIndicator( + modifier = Modifier.width(64.dp), + color = MaterialTheme.colorScheme.secondary, + trackColor = MaterialTheme.colorScheme.surfaceVariant, + ) } } } diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreenState.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreenState.kt index ce13df1..88e05ca 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreenState.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreenState.kt @@ -3,4 +3,4 @@ package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.va import com.github.nullptroma.wallenc.domain.datatypes.Tree import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo -data class LocalVaultScreenState(val storagesList: List>) \ No newline at end of file +data class LocalVaultScreenState(val storagesList: List>, val isLoading: Boolean) \ No newline at end of file diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt index f06b122..4019cd9 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt @@ -29,15 +29,43 @@ class LocalVaultViewModel @Inject constructor( private val manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase, private val renameStorageUseCase: RenameStorageUseCase, private val logger: ILogger -) : ViewModelBase(LocalVaultScreenState(listOf())) { +) : ViewModelBase(LocalVaultScreenState(listOf(), true)) { + private var _taskCount: Int = 0 + private var tasksCount + get() = _taskCount + set(value) { + _taskCount = value + updateStateLoading() + } + + private var _isLoading: Boolean = false + private var isLoading + get() = _isLoading + set(value) { + _isLoading = value + updateStateLoading() + } + init { + collectFlows() + } + + private fun updateStateLoading() { + updateState(state.value.copy( + isLoading = this.isLoading || this.tasksCount > 0 + )) + } + + private fun collectFlows() { viewModelScope.launch { manageLocalVaultUseCase.localStorages.combine(getOpenedStoragesUseCase.openedStorages) { local, opened -> + if(local == null || opened == null) + return@combine null val list = mutableListOf>() for (storage in local) { var tree = Tree(storage) list.add(tree) - while(opened != null && opened.containsKey(tree.value.uuid)) { + while(opened.containsKey(tree.value.uuid)) { val child = opened.getValue(tree.value.uuid) val nextTree = Tree(child) tree.children = listOf(nextTree) @@ -46,17 +74,13 @@ class LocalVaultViewModel @Inject constructor( } return@combine list }.collectLatest { + isLoading = it == null val newState = state.value.copy( - storagesList = it + storagesList = it ?: listOf() ) updateState(newState) } } - viewModelScope.launch { - getOpenedStoragesUseCase.openedStorages.collectLatest { - logger.debug("ViewModel", "Collected opened: ${it?.size}") - } - } } fun printStorageInfoToLog(storage: IStorageInfo) { @@ -80,8 +104,10 @@ class LocalVaultViewModel @Inject constructor( } fun createStorage() { + tasksCount++ viewModelScope.launch { manageLocalVaultUseCase.createStorage() + tasksCount-- } } @@ -89,6 +115,7 @@ class LocalVaultViewModel @Inject constructor( fun enableEncryptionAndOpenStorage(storage: IStorageInfo) { if(runningStorages.contains(storage)) return + tasksCount++ runningStorages.add(storage) val key = EncryptKey("Hello") viewModelScope.launch { @@ -98,6 +125,7 @@ class LocalVaultViewModel @Inject constructor( } finally { runningStorages.remove(storage) + tasksCount-- } } }