From f056d6e89b9a9c19ee0171df95684295a9b4a1df 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: Tue, 28 Jan 2025 21:35:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BE=D1=88=D0=B5=D0=BB=D1=8C=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../data/storages/local/LocalStorage.kt | 2 +- .../wallenc/data/vaults/LocalVault.kt | 17 +++- .../usecases/ManageLocalVaultUseCase.kt | 7 ++ .../wallenc/presentation/elements/Dialogs.kt | 95 +++++++++++++++++++ .../presentation/elements/StorageTree.kt | 78 ++++++--------- .../screens/local/vault/LocalVaultScreen.kt | 3 + .../local/vault/LocalVaultViewModel.kt | 6 ++ presentation/src/main/res/values/strings.xml | 3 + 9 files changed, 157 insertions(+), 55 deletions(-) create mode 100644 presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/Dialogs.kt diff --git a/.gitignore b/.gitignore index 347e252..cb5a6ad 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ google-services.json # Android Profiling *.hprof +/app/release diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/local/LocalStorage.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/local/LocalStorage.kt index df26fff..44ffa5c 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/local/LocalStorage.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/local/LocalStorage.kt @@ -16,7 +16,7 @@ import java.util.UUID class LocalStorage( override val uuid: UUID, - absolutePath: String, + val absolutePath: String, private val ioDispatcher: CoroutineDispatcher, ) : IStorage { override val size: StateFlow diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/LocalVault.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/LocalVault.kt index 6d3e8a5..73096a4 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/LocalVault.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/vaults/LocalVault.kt @@ -23,7 +23,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context override val uuid: UUID get() = TODO("Not yet implemented") - private val _storages = MutableStateFlow(listOf()) + private val _storages = MutableStateFlow(listOf()) override val storages: StateFlow> = _storages private val _isAvailable = MutableStateFlow(false) @@ -48,7 +48,7 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context private suspend fun readStorages() { val path = path.value if (path == null || !_isAvailable.value) - return + throw Exception("Not available") val dirs = path.listFiles()?.filter { it.isDirectory } if (dirs != null) { @@ -84,6 +84,17 @@ class LocalVault(private val ioDispatcher: CoroutineDispatcher, context: Context } override suspend fun remove(storage: IStorage) = withContext(ioDispatcher) { - TODO("Not yet implemented") + val path = path.value + if (path == null || !_isAvailable.value) + throw Exception("Not available") + + val curStorages = _storages.value.toMutableList() + val index = curStorages.indexOf(storage) + if(index != -1) { + val localStorage = curStorages[index] + curStorages.removeAt(index) + _storages.value = curStorages + File(localStorage.absolutePath).deleteRecursively() + } } } \ No newline at end of file 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 c78f3bc..e8a3b33 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 @@ -2,6 +2,7 @@ package com.github.nullptroma.wallenc.domain.usecases import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey import com.github.nullptroma.wallenc.domain.encrypt.Encryptor +import com.github.nullptroma.wallenc.domain.interfaces.IStorage import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager @@ -21,4 +22,10 @@ class ManageLocalVaultUseCase(private val manager: IVaultsManager, private val u val storage = manager.localVault.createStorage(encInfo) unlockManager.open(storage, key) } + + suspend fun remove(storage: IStorageInfo) { + when(storage) { + is IStorage -> manager.localVault.remove(storage) + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/Dialogs.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/Dialogs.kt new file mode 100644 index 0000000..93bb6e6 --- /dev/null +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/Dialogs.kt @@ -0,0 +1,95 @@ +package com.github.nullptroma.wallenc.presentation.elements + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TextEditCancelOkDialog(onDismiss: () -> Unit, onConfirmation: (String) -> Unit, title: String, startString: String = "") { + var name by remember { mutableStateOf(startString) } + val focusRequester = remember { FocusRequester() } + BasicAlertDialog( + onDismissRequest = { onDismiss() } + ) { + Card { + Column(modifier = Modifier.padding(12.dp)) { + Text(title, style = MaterialTheme.typography.titleLarge) + TextField(modifier = Modifier.focusRequester(focusRequester), value = name, onValueChange = { + name = it + }) + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Button(modifier = Modifier.weight(1f), onClick = onDismiss) { + Text("Cancel") + } + Spacer(modifier = Modifier.width(12.dp)) + Button(modifier = Modifier.weight(1f), onClick = { + onConfirmation(name) + }) { + Text("Ok") + } + } + } + } + } + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ConfirmationCancelOkDialog(onDismiss: () -> Unit, onConfirmation: () -> Unit, title: String) { + BasicAlertDialog( + onDismissRequest = { onDismiss() } + ) { + Card { + Column(modifier = Modifier.padding(12.dp)) { + Text(title, style = MaterialTheme.typography.titleLarge) + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Button(modifier = Modifier.weight(1f), onClick = onDismiss) { + Text("Cancel") + } + Spacer(modifier = Modifier.width(12.dp)) + Button(modifier = Modifier.weight(1f), onClick = { + onConfirmation() + }) { + Text("Ok") + } + } + } + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt index 2a0f4a6..b8f475a 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt @@ -1,38 +1,26 @@ package com.github.nullptroma.wallenc.presentation.elements -import android.app.Dialog -import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material3.BasicAlertDialog -import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Divider import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -41,15 +29,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.PlatformTextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.github.nullptroma.wallenc.domain.datatypes.Tree import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo @@ -61,6 +45,7 @@ fun StorageTree( tree: Tree, onClick: (Tree) -> Unit, onRename: (Tree, String) -> Unit, + onRemove: (Tree) -> Unit, ) { val cur = tree.value val cardShape = RoundedCornerShape(30.dp) @@ -100,6 +85,7 @@ fun StorageTree( Box(modifier = Modifier.padding(0.dp, 8.dp, 8.dp, 0.dp)) { var expanded by remember { mutableStateOf(false) } var showRenameDialog by remember { mutableStateOf(false) } + var showRemoveConfirmationDiaglog by remember { mutableStateOf(false) } IconButton(onClick = { expanded = !expanded }) { Icon( Icons.Default.MoreVert, @@ -117,16 +103,38 @@ fun StorageTree( }, text = { Text(stringResource(R.string.rename)) } ) + HorizontalDivider() + DropdownMenuItem( + onClick = { + expanded = false + showRemoveConfirmationDiaglog = true; + }, + text = { Text(stringResource(R.string.remove)) } + ) } if (showRenameDialog) { - RenameDialog( + TextEditCancelOkDialog( onDismiss = { showRenameDialog = false }, onConfirmation = { newName -> showRenameDialog = false onRename(tree, newName) }, - startName = tree.value.metaInfo.value.name ?: "" + title = stringResource(R.string.new_name_title), + startString = metaInfo.name ?: "" + ) + } + + if (showRemoveConfirmationDiaglog) { + ConfirmationCancelOkDialog( + onDismiss = { + showRemoveConfirmationDiaglog = false + }, + onConfirmation = { + showRemoveConfirmationDiaglog = false + onRemove(tree) + }, + title = stringResource(R.string.remove_confirmation_dialog, metaInfo.name ?: "") ) } } @@ -149,40 +157,8 @@ fun StorageTree( } } for (i in tree.children ?: listOf()) { - StorageTree(Modifier.padding(16.dp, 0.dp, 0.dp, 0.dp), i, onClick, onRename) + StorageTree(Modifier.padding(16.dp, 0.dp, 0.dp, 0.dp), i, onClick, onRename, onRemove) } } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun RenameDialog(onDismiss: () -> Unit, onConfirmation: (String) -> Unit, startName: String = "") { - var name by remember { mutableStateOf(startName) } - BasicAlertDialog( - onDismissRequest = { onDismiss() } - ) { - Card { - Column(modifier = Modifier.padding(12.dp)) { - Text("New name", style = MaterialTheme.typography.titleLarge) - TextField(name, { - name = it - }) - Spacer(modifier = Modifier.height(24.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - ) { - Button(modifier = Modifier.weight(1f), onClick = onDismiss) { - Text("Cancel") - } - Spacer(modifier = Modifier.width(12.dp)) - Button(modifier = Modifier.weight(1f), onClick = { - onConfirmation(name) - }) { - Text("Ok") - } - } - } - } - } -} 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 f0b2441..17311c6 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 @@ -78,6 +78,9 @@ fun LocalVaultScreen( }, onRename = { tree, newName -> viewModel.rename(tree.value, newName) + }, + onRemove = { tree -> + viewModel.remove(tree.value) } ) } 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 95bc770..98c3a29 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 @@ -82,4 +82,10 @@ class LocalVaultViewModel @Inject constructor( renameStorageUseCase.rename(storage, newName) } } + + fun remove(storage: IStorageInfo) { + viewModelScope.launch { + manageLocalVaultUseCase.remove(storage) + } + } } \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 8f6c55a..57aca8b 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -9,5 +9,8 @@ <noname> Show storage item menu Rename + Remove + New name + Delete storage "%1$s"? \ No newline at end of file