Полное управление шифрованием и ключами
This commit is contained in:
@@ -8,6 +8,7 @@ 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.Checkbox
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
@@ -21,6 +22,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
@@ -92,4 +94,99 @@ fun ConfirmationCancelOkDialog(onDismiss: () -> Unit, onConfirmation: () -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EncryptionSetupDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirmation: (password: String, encryptPath: Boolean) -> Unit,
|
||||
) {
|
||||
var password by remember { mutableStateOf("") }
|
||||
var encryptPath by remember { mutableStateOf(false) }
|
||||
BasicAlertDialog(onDismissRequest = onDismiss) {
|
||||
Card {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Text("Enable encryption", style = MaterialTheme.typography.titleLarge)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
TextField(value = password, onValueChange = { password = it }, label = { Text("Password") })
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = encryptPath, onCheckedChange = { encryptPath = it })
|
||||
Text("Encrypt paths")
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.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(password, encryptPath) },
|
||||
enabled = password.isNotEmpty()
|
||||
) { Text("Apply") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OpenEncryptedStorageDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirmation: (password: String, rememberPassword: Boolean) -> Unit,
|
||||
) {
|
||||
var password by remember { mutableStateOf("") }
|
||||
var rememberPassword by remember { mutableStateOf(false) }
|
||||
BasicAlertDialog(onDismissRequest = onDismiss) {
|
||||
Card {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Text("Open encrypted storage", style = MaterialTheme.typography.titleLarge)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
TextField(value = password, onValueChange = { password = it }, label = { Text("Password") })
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = rememberPassword, onCheckedChange = { rememberPassword = it })
|
||||
Text("Remember password")
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.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(password, rememberPassword) },
|
||||
enabled = password.isNotEmpty()
|
||||
) { Text("Open") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun StorageEncryptionActionsDialog(
|
||||
onDismiss: () -> Unit,
|
||||
title: String,
|
||||
isOpened: Boolean,
|
||||
onOpen: () -> Unit,
|
||||
onClose: () -> Unit,
|
||||
onDisable: () -> Unit,
|
||||
) {
|
||||
BasicAlertDialog(onDismissRequest = onDismiss) {
|
||||
Card {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Text(title, style = MaterialTheme.typography.titleLarge)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
if (isOpened) {
|
||||
Button(onClick = onClose, modifier = Modifier.fillMaxWidth()) { Text("Close") }
|
||||
} else {
|
||||
Button(onClick = onOpen, modifier = Modifier.fillMaxWidth()) { Text("Open") }
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(onClick = onDisable, modifier = Modifier.fillMaxWidth()) { Text("Disable encryption") }
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(onClick = onDismiss, modifier = Modifier.fillMaxWidth()) { Text("Done") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material.icons.filled.LockOpen
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
@@ -60,7 +61,12 @@ fun StorageTree(
|
||||
onClick: (Tree<IStorageInfo>) -> Unit,
|
||||
onRename: (Tree<IStorageInfo>, String) -> Unit,
|
||||
onRemove: (Tree<IStorageInfo>) -> Unit,
|
||||
onEncrypt: (Tree<IStorageInfo>) -> Unit,
|
||||
onEncrypt: (Tree<IStorageInfo>, String, Boolean) -> Unit,
|
||||
onOpenEncrypted: (Tree<IStorageInfo>, String, Boolean) -> Unit,
|
||||
onCloseEncrypted: (Tree<IStorageInfo>) -> Unit,
|
||||
onDisableEncryption: (Tree<IStorageInfo>) -> Unit,
|
||||
getStatusText: (Tree<IStorageInfo>) -> String,
|
||||
isEncryptionOpened: (Tree<IStorageInfo>) -> Boolean,
|
||||
) {
|
||||
val cur = tree.value
|
||||
val available by cur.isAvailable.collectAsStateWithLifecycle()
|
||||
@@ -68,6 +74,8 @@ fun StorageTree(
|
||||
val size by cur.size.collectAsStateWithLifecycle()
|
||||
val metaInfo by cur.metaInfo.collectAsStateWithLifecycle()
|
||||
val isAvailable by cur.isAvailable.collectAsStateWithLifecycle()
|
||||
val isEncrypted = metaInfo.encInfo != null
|
||||
val isOpened = isEncryptionOpened(tree)
|
||||
val borderColor =
|
||||
if (cur.isVirtualStorage) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.primary
|
||||
Column(modifier) {
|
||||
@@ -120,10 +128,13 @@ fun StorageTree(
|
||||
modifier = Modifier,
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var showRenameDialog by remember { mutableStateOf(false) }
|
||||
var showRemoveConfirmDialog by remember { mutableStateOf(false) }
|
||||
var showLockDialog by remember { mutableStateOf(false) }
|
||||
var showSetupEncryptionDialog by remember { mutableStateOf(false) }
|
||||
var showOpenEncryptionDialog by remember { mutableStateOf(false) }
|
||||
Box(modifier = Modifier.padding(0.dp, 0.dp, 0.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,
|
||||
@@ -145,10 +156,20 @@ fun StorageTree(
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
expanded = false
|
||||
showRemoveConfirmationDiaglog = true;
|
||||
showRemoveConfirmDialog = true
|
||||
},
|
||||
text = { Text(stringResource(R.string.remove)) }
|
||||
)
|
||||
if (!isEncrypted) {
|
||||
HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
expanded = false
|
||||
showSetupEncryptionDialog = true
|
||||
},
|
||||
text = { Text(stringResource(R.string.encrypt)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showRenameDialog) {
|
||||
@@ -163,26 +184,78 @@ fun StorageTree(
|
||||
)
|
||||
}
|
||||
|
||||
if (showRemoveConfirmationDiaglog) {
|
||||
if (showRemoveConfirmDialog) {
|
||||
ConfirmationCancelOkDialog(
|
||||
onDismiss = {
|
||||
showRemoveConfirmationDiaglog = false
|
||||
},
|
||||
onConfirmation = {
|
||||
showRemoveConfirmationDiaglog = false
|
||||
onRemove(tree)
|
||||
},
|
||||
onDismiss = { showRemoveConfirmDialog = false },
|
||||
title = stringResource(
|
||||
R.string.remove_confirmation_dialog,
|
||||
metaInfo.name ?: "<noname>"
|
||||
)
|
||||
),
|
||||
onConfirmation = {
|
||||
showRemoveConfirmDialog = false
|
||||
onRemove(tree)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showLockDialog) {
|
||||
StorageEncryptionActionsDialog(
|
||||
onDismiss = { showLockDialog = false },
|
||||
title = metaInfo.name ?: stringResource(R.string.no_name),
|
||||
isOpened = isOpened,
|
||||
onOpen = {
|
||||
showLockDialog = false
|
||||
showOpenEncryptionDialog = true
|
||||
},
|
||||
onClose = {
|
||||
showLockDialog = false
|
||||
onCloseEncrypted(tree)
|
||||
},
|
||||
onDisable = {
|
||||
showLockDialog = false
|
||||
onDisableEncryption(tree)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showSetupEncryptionDialog) {
|
||||
EncryptionSetupDialog(
|
||||
onDismiss = { showSetupEncryptionDialog = false },
|
||||
onConfirmation = { password, encryptPath ->
|
||||
showSetupEncryptionDialog = false
|
||||
onEncrypt(tree, password, encryptPath)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showOpenEncryptionDialog) {
|
||||
OpenEncryptedStorageDialog(
|
||||
onDismiss = { showOpenEncryptionDialog = false },
|
||||
onConfirmation = { password, rememberPassword ->
|
||||
showOpenEncryptionDialog = false
|
||||
onOpenEncrypted(tree, password, rememberPassword)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Button(onClick = { onEncrypt(tree) }, enabled = metaInfo.encInfo == null) {
|
||||
Text("Encrypt")
|
||||
if (isEncrypted) {
|
||||
IconButton(onClick = { showLockDialog = true }) {
|
||||
Icon(
|
||||
if (isOpened) Icons.Default.LockOpen else Icons.Default.Lock,
|
||||
contentDescription = stringResource(R.string.storage_lock_actions)
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(0.dp, 0.dp, 12.dp, 0.dp)
|
||||
.align(Alignment.End),
|
||||
text = getStatusText(tree),
|
||||
textAlign = TextAlign.End,
|
||||
fontSize = 11.sp,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -221,7 +294,12 @@ fun StorageTree(
|
||||
onClick,
|
||||
onRename,
|
||||
onRemove,
|
||||
onEncrypt
|
||||
onEncrypt,
|
||||
onOpenEncrypted,
|
||||
onCloseEncrypted,
|
||||
onDisableEncryption,
|
||||
getStatusText,
|
||||
isEncryptionOpened
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault
|
||||
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Toast
|
||||
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
|
||||
@@ -23,16 +21,18 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.platform.LocalContext
|
||||
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
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
||||
@Composable
|
||||
fun LocalVaultScreen(
|
||||
@@ -42,6 +42,13 @@ fun LocalVaultScreen(
|
||||
) {
|
||||
|
||||
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.messages.collect { message ->
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
Box {
|
||||
Scaffold(modifier = modifier, contentWindowInsets = WindowInsets(0.dp), floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
@@ -66,9 +73,24 @@ fun LocalVaultScreen(
|
||||
onRemove = { tree ->
|
||||
viewModel.remove(tree.value)
|
||||
},
|
||||
onEncrypt = { tree ->
|
||||
viewModel.enableEncryptionAndOpenStorage(tree.value)
|
||||
}
|
||||
onEncrypt = { tree, password, encryptPath ->
|
||||
viewModel.enableEncryption(tree.value, password, encryptPath)
|
||||
},
|
||||
onOpenEncrypted = { tree, password, remember ->
|
||||
viewModel.openEncryptedStorage(tree.value, password, remember)
|
||||
},
|
||||
onCloseEncrypted = { tree ->
|
||||
viewModel.closeEncryptedStorage(tree.value)
|
||||
},
|
||||
onDisableEncryption = { tree ->
|
||||
viewModel.disableEncryption(tree.value)
|
||||
},
|
||||
getStatusText = { tree ->
|
||||
viewModel.getStorageStatus(tree.value)
|
||||
},
|
||||
isEncryptionOpened = { tree ->
|
||||
viewModel.isEncryptionSessionOpen(tree.value)
|
||||
},
|
||||
)
|
||||
}
|
||||
item {
|
||||
|
||||
@@ -7,7 +7,6 @@ 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.enums.StorageDeletionPolicy
|
||||
import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase
|
||||
import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase
|
||||
import com.github.nullptroma.wallenc.domain.usecases.ManageStoragesEncryptionUseCase
|
||||
@@ -19,6 +18,8 @@ import com.github.nullptroma.wallenc.presentation.extensions.toPrintable
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.measureTimeMillis
|
||||
@@ -33,6 +34,9 @@ class LocalVaultViewModel @Inject constructor(
|
||||
private val renameStorageUseCase: RenameStorageUseCase,
|
||||
private val logger: ILogger
|
||||
) : ViewModelBase<LocalVaultScreenState>(LocalVaultScreenState(listOf(), true)) {
|
||||
private val _messages = MutableSharedFlow<String>()
|
||||
val messages: SharedFlow<String> = _messages
|
||||
|
||||
private var _taskCount: Int = 0
|
||||
private var tasksCount
|
||||
get() = _taskCount
|
||||
@@ -114,37 +118,105 @@ class LocalVaultViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val runningStorages = mutableSetOf<IStorageInfo>()
|
||||
fun enableEncryptionAndOpenStorage(storage: IStorageInfo) {
|
||||
if(runningStorages.contains(storage))
|
||||
private val runningStorages = mutableSetOf<java.util.UUID>()
|
||||
fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) {
|
||||
val id = storage.uuid
|
||||
if (runningStorages.contains(id))
|
||||
return
|
||||
tasksCount++
|
||||
runningStorages.add(storage)
|
||||
val key = EncryptKey("Hello")
|
||||
runningStorages.add(id)
|
||||
val key = EncryptKey(password)
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
manageStoragesEncryptionUseCase.enableEncryption(storage, key, false)
|
||||
manageStoragesEncryptionUseCase.openStorage(storage, key)
|
||||
when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
|
||||
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
|
||||
manageStoragesEncryptionUseCase.openStorage(storage, key, true)
|
||||
_messages.emit("Encryption enabled")
|
||||
}
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted -> {
|
||||
_messages.emit("Storage is already encrypted")
|
||||
}
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.StorageIsNotEmpty -> {
|
||||
_messages.emit("Storage is not empty")
|
||||
}
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.StorageStateUnknown -> {
|
||||
_messages.emit("Cannot determine whether storage is empty")
|
||||
}
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.UnsupportedStorageType -> {
|
||||
_messages.emit("Unsupported storage type")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_messages.emit(e.message ?: "Failed to enable encryption")
|
||||
}
|
||||
finally {
|
||||
runningStorages.remove(storage)
|
||||
runningStorages.remove(id)
|
||||
tasksCount--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openEncryptedStorage(storage: IStorageInfo, password: String, rememberPassword: Boolean) {
|
||||
val id = storage.uuid
|
||||
if (runningStorages.contains(id)) return
|
||||
tasksCount++
|
||||
runningStorages.add(id)
|
||||
val key = EncryptKey(password)
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
manageStoragesEncryptionUseCase.openStorage(storage, key, rememberPassword)
|
||||
} catch (e: Exception) {
|
||||
_messages.emit(e.message ?: "Failed to open encrypted storage")
|
||||
} finally {
|
||||
runningStorages.remove(id)
|
||||
tasksCount--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun closeEncryptedStorage(storage: IStorageInfo) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
manageStoragesEncryptionUseCase.closeStorage(storage)
|
||||
} catch (e: Exception) {
|
||||
_messages.emit(e.message ?: "Failed to close encrypted storage")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun disableEncryption(storage: IStorageInfo) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
manageStoragesEncryptionUseCase.disableEncryption(storage)
|
||||
_messages.emit("Encryption disabled")
|
||||
} catch (e: Exception) {
|
||||
_messages.emit(e.message ?: "Failed to disable encryption")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun rename(storage: IStorageInfo, newName: String) {
|
||||
viewModelScope.launch {
|
||||
renameStorageUseCase.rename(storage, newName)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(
|
||||
storage: IStorageInfo,
|
||||
policy: StorageDeletionPolicy = StorageDeletionPolicy.REMOVE_PHYSICAL,
|
||||
) {
|
||||
fun remove(storage: IStorageInfo) {
|
||||
viewModelScope.launch {
|
||||
removeStorageUseCase.remove(storage, policy)
|
||||
removeStorageUseCase.remove(storage)
|
||||
}
|
||||
}
|
||||
|
||||
fun getStorageStatus(storage: IStorageInfo): String {
|
||||
val encrypted = storage.metaInfo.value.encInfo != null
|
||||
if (!encrypted) return "Not encrypted"
|
||||
val opened = isEncryptionSessionOpen(storage)
|
||||
return if (opened) "Encrypted (opened)" else "Encrypted (closed)"
|
||||
}
|
||||
|
||||
fun isEncryptionSessionOpen(storage: IStorageInfo): Boolean {
|
||||
val openedMap = getOpenedStoragesUseCase.openedStorages.value
|
||||
return openedMap.containsKey(storage.uuid)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@
|
||||
<string name="show_storage_item_menu">Show storage item menu</string>
|
||||
<string name="rename">Rename</string>
|
||||
<string name="remove">Remove</string>
|
||||
<string name="encrypt">Encrypt</string>
|
||||
<string name="new_name_title">New name</string>
|
||||
<string name="remove_confirmation_dialog">Delete storage "%1$s"?</string>
|
||||
<string name="storage_lock_actions">Storage encryption actions</string>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user