Работа с 2fa и секретами перенесена в tasks
This commit is contained in:
@@ -59,7 +59,10 @@ fun TextSecretDetailsScreen(
|
|||||||
text = secret.title,
|
text = secret.title,
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
)
|
)
|
||||||
TextButton(onClick = { onEdit(secret.id) }, enabled = uiState.isAvailable) {
|
TextButton(
|
||||||
|
onClick = { onEdit(secret.id) },
|
||||||
|
enabled = uiState.isAvailable && !uiState.isMutating,
|
||||||
|
) {
|
||||||
Text(stringResource(R.string.edit))
|
Text(stringResource(R.string.edit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +97,7 @@ fun TextSecretDetailsScreen(
|
|||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = { viewModel.delete(onDeleted) },
|
onClick = { viewModel.delete(onDeleted) },
|
||||||
enabled = uiState.isAvailable,
|
enabled = uiState.isAvailable && !uiState.isMutating,
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.remove))
|
Text(stringResource(R.string.remove))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
|
|||||||
data class TextSecretDetailsScreenState(
|
data class TextSecretDetailsScreenState(
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val isAvailable: Boolean = false,
|
val isAvailable: Boolean = false,
|
||||||
|
val isMutating: Boolean = false,
|
||||||
val secret: TextSecretRecord? = null,
|
val secret: TextSecretRecord? = null,
|
||||||
val errorMessage: String? = null,
|
val errorMessage: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,11 +2,17 @@ package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.secrets
|
|||||||
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
|
||||||
|
import com.github.nullptroma.wallenc.domain.tasks.TaskRunState
|
||||||
|
import com.github.nullptroma.wallenc.ui.R
|
||||||
import com.github.nullptroma.wallenc.ui.ViewModelBase
|
import com.github.nullptroma.wallenc.ui.ViewModelBase
|
||||||
|
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
|
||||||
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.requireStorageUuid
|
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.requireStorageUuid
|
||||||
import com.github.nullptroma.wallenc.usecases.FindStorageUseCase
|
import com.github.nullptroma.wallenc.usecases.FindStorageUseCase
|
||||||
import com.github.nullptroma.wallenc.usecases.ManageTextSecretsUseCase
|
import com.github.nullptroma.wallenc.usecases.ManageTextSecretsUseCase
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -17,6 +23,8 @@ class TextSecretDetailsViewModel @Inject constructor(
|
|||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val findStorageUseCase: FindStorageUseCase,
|
private val findStorageUseCase: FindStorageUseCase,
|
||||||
private val manageTextSecretsUseCase: ManageTextSecretsUseCase,
|
private val manageTextSecretsUseCase: ManageTextSecretsUseCase,
|
||||||
|
private val taskOrchestrator: ITaskOrchestrator,
|
||||||
|
private val uiStrings: UiStringResolver,
|
||||||
) : ViewModelBase<TextSecretDetailsScreenState>(TextSecretDetailsScreenState()) {
|
) : ViewModelBase<TextSecretDetailsScreenState>(TextSecretDetailsScreenState()) {
|
||||||
|
|
||||||
private val storageUuid = savedStateHandle.requireStorageUuid()
|
private val storageUuid = savedStateHandle.requireStorageUuid()
|
||||||
@@ -44,10 +52,16 @@ class TextSecretDetailsViewModel @Inject constructor(
|
|||||||
manageTextSecretsUseCase.observe(storage).map { list ->
|
manageTextSecretsUseCase.observe(storage).map { list ->
|
||||||
list.firstOrNull { it.id == secretId }
|
list.firstOrNull { it.id == secretId }
|
||||||
},
|
},
|
||||||
) { available, secret ->
|
taskOrchestrator.pipelineState.map { pipe ->
|
||||||
|
pipe.tasks.any { t ->
|
||||||
|
t.busyStorageUuid == storage.uuid && isTaskActive(t.state)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { available, secret, isMutating ->
|
||||||
state.value.copy(
|
state.value.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
isAvailable = available,
|
isAvailable = available,
|
||||||
|
isMutating = isMutating,
|
||||||
secret = secret,
|
secret = secret,
|
||||||
errorMessage = if (secret == null) "Secret not found" else null,
|
errorMessage = if (secret == null) "Secret not found" else null,
|
||||||
)
|
)
|
||||||
@@ -60,10 +74,28 @@ class TextSecretDetailsViewModel @Inject constructor(
|
|||||||
fun delete(onDeleted: () -> Unit) {
|
fun delete(onDeleted: () -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val storage = findStorageUseCase.find(storageUuid) ?: return@launch
|
val storage = findStorageUseCase.find(storageUuid) ?: return@launch
|
||||||
val removed = manageTextSecretsUseCase.delete(storage, secretId)
|
val result = CompletableDeferred<Result<Boolean>>()
|
||||||
|
taskOrchestrator.enqueue(
|
||||||
|
title = uiStrings(R.string.task_title_delete_text_secret),
|
||||||
|
dispatcher = Dispatchers.IO,
|
||||||
|
busyStorageUuid = storage.uuid,
|
||||||
|
work = { _ ->
|
||||||
|
try {
|
||||||
|
val removed = manageTextSecretsUseCase.delete(storage, secretId)
|
||||||
|
result.complete(Result.success(removed))
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
result.complete(Result.failure(t))
|
||||||
|
throw t
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val removed = result.await().getOrElse { false }
|
||||||
if (removed) {
|
if (removed) {
|
||||||
onDeleted()
|
onDeleted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isTaskActive(state: TaskRunState): Boolean =
|
||||||
|
state is TaskRunState.Queued || state is TaskRunState.Running
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ fun TextSecretEditScreen(
|
|||||||
label = { Text(stringResource(R.string.text_secret_item_value)) },
|
label = { Text(stringResource(R.string.text_secret_item_value)) },
|
||||||
)
|
)
|
||||||
IconButton(
|
IconButton(
|
||||||
|
enabled = !uiState.isMutating,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (items.size > 1) {
|
if (items.size > 1) {
|
||||||
items.removeAt(index)
|
items.removeAt(index)
|
||||||
@@ -121,7 +122,10 @@ fun TextSecretEditScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
TextButton(onClick = { items.add(TextSecretEntryRecord(label = null, value = "")) }) {
|
TextButton(
|
||||||
|
onClick = { items.add(TextSecretEntryRecord(label = null, value = "")) },
|
||||||
|
enabled = !uiState.isMutating,
|
||||||
|
) {
|
||||||
Icon(Icons.Default.Add, contentDescription = null)
|
Icon(Icons.Default.Add, contentDescription = null)
|
||||||
Text(stringResource(R.string.text_secret_add_item))
|
Text(stringResource(R.string.text_secret_add_item))
|
||||||
}
|
}
|
||||||
@@ -133,7 +137,7 @@ fun TextSecretEditScreen(
|
|||||||
onSaved = currentOnSaved,
|
onSaved = currentOnSaved,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
enabled = title.isNotBlank(),
|
enabled = title.isNotBlank() && !uiState.isMutating,
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.save))
|
Text(stringResource(R.string.save))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
|
|||||||
data class TextSecretEditScreenState(
|
data class TextSecretEditScreenState(
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val isAvailable: Boolean = false,
|
val isAvailable: Boolean = false,
|
||||||
|
val isMutating: Boolean = false,
|
||||||
val initialSecret: TextSecretRecord? = null,
|
val initialSecret: TextSecretRecord? = null,
|
||||||
val errorMessage: String? = null,
|
val errorMessage: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,13 +4,21 @@ import androidx.lifecycle.SavedStateHandle
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretEntryRecord
|
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretEntryRecord
|
||||||
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
|
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
|
||||||
|
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
|
||||||
|
import com.github.nullptroma.wallenc.domain.tasks.TaskRunState
|
||||||
|
import com.github.nullptroma.wallenc.ui.R
|
||||||
import com.github.nullptroma.wallenc.ui.ViewModelBase
|
import com.github.nullptroma.wallenc.ui.ViewModelBase
|
||||||
|
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
|
||||||
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.optionalSecretId
|
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.optionalSecretId
|
||||||
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.requireStorageUuid
|
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.requireStorageUuid
|
||||||
import com.github.nullptroma.wallenc.usecases.FindStorageUseCase
|
import com.github.nullptroma.wallenc.usecases.FindStorageUseCase
|
||||||
import com.github.nullptroma.wallenc.usecases.ManageTextSecretsUseCase
|
import com.github.nullptroma.wallenc.usecases.ManageTextSecretsUseCase
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -19,6 +27,8 @@ class TextSecretEditViewModel @Inject constructor(
|
|||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val findStorageUseCase: FindStorageUseCase,
|
private val findStorageUseCase: FindStorageUseCase,
|
||||||
private val manageTextSecretsUseCase: ManageTextSecretsUseCase,
|
private val manageTextSecretsUseCase: ManageTextSecretsUseCase,
|
||||||
|
private val taskOrchestrator: ITaskOrchestrator,
|
||||||
|
private val uiStrings: UiStringResolver,
|
||||||
) : ViewModelBase<TextSecretEditScreenState>(TextSecretEditScreenState()) {
|
) : ViewModelBase<TextSecretEditScreenState>(TextSecretEditScreenState()) {
|
||||||
|
|
||||||
private val storageUuid = savedStateHandle.requireStorageUuid()
|
private val storageUuid = savedStateHandle.requireStorageUuid()
|
||||||
@@ -40,15 +50,26 @@ class TextSecretEditViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val initial = secretId?.let { manageTextSecretsUseCase.get(storage, it) }
|
val initial = secretId?.let { id -> manageTextSecretsUseCase.get(storage, id) }
|
||||||
updateState(
|
combine(
|
||||||
|
storage.isAvailable,
|
||||||
|
taskOrchestrator.pipelineState.map { pipe ->
|
||||||
|
pipe.tasks.any { t ->
|
||||||
|
t.busyStorageUuid == storage.uuid && isTaskActive(t.state)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
flowOf(initial),
|
||||||
|
) { available, isMutating, currentSecret ->
|
||||||
state.value.copy(
|
state.value.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
isAvailable = storage.isAvailable.first(),
|
isAvailable = available,
|
||||||
initialSecret = initial,
|
isMutating = isMutating,
|
||||||
|
initialSecret = currentSecret,
|
||||||
errorMessage = null,
|
errorMessage = null,
|
||||||
),
|
)
|
||||||
)
|
}.collect { ui ->
|
||||||
|
updateState(ui)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,24 +81,42 @@ class TextSecretEditViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val storage = findStorageUseCase.find(storageUuid) ?: return@launch
|
val storage = findStorageUseCase.find(storageUuid) ?: return@launch
|
||||||
val existingId = secretId
|
val existingId = secretId
|
||||||
if (existingId == null) {
|
val result = CompletableDeferred<Result<String>>()
|
||||||
val created = manageTextSecretsUseCase.create(
|
taskOrchestrator.enqueue(
|
||||||
storageInfo = storage,
|
title = uiStrings(R.string.task_title_save_text_secret),
|
||||||
title = title,
|
dispatcher = Dispatchers.IO,
|
||||||
items = items,
|
busyStorageUuid = storage.uuid,
|
||||||
)
|
work = { _ ->
|
||||||
onSaved(created.id)
|
try {
|
||||||
} else {
|
if (existingId == null) {
|
||||||
manageTextSecretsUseCase.update(
|
val created = manageTextSecretsUseCase.create(
|
||||||
storageInfo = storage,
|
storageInfo = storage,
|
||||||
secret = TextSecretRecord(
|
title = title,
|
||||||
id = existingId,
|
items = items,
|
||||||
title = title,
|
)
|
||||||
items = items,
|
result.complete(Result.success(created.id))
|
||||||
),
|
} else {
|
||||||
)
|
manageTextSecretsUseCase.update(
|
||||||
onSaved(existingId)
|
storageInfo = storage,
|
||||||
}
|
secret = TextSecretRecord(
|
||||||
|
id = existingId,
|
||||||
|
title = title,
|
||||||
|
items = items,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
result.complete(Result.success(existingId))
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
result.complete(Result.failure(t))
|
||||||
|
throw t
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val savedId = result.await().getOrNull() ?: return@launch
|
||||||
|
onSaved(savedId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isTaskActive(state: TaskRunState): Boolean =
|
||||||
|
state is TaskRunState.Queued || state is TaskRunState.Running
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,11 +57,11 @@ fun TwoFaTokensScreen(
|
|||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (uiState.isAvailable) {
|
if (uiState.isAvailable && !uiState.isMutating) {
|
||||||
creating = true
|
creating = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.alpha(if (uiState.isAvailable) 1f else 0.5f),
|
modifier = Modifier.alpha(if (uiState.isAvailable && !uiState.isMutating) 1f else 0.5f),
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.two_fa_add_token))
|
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.two_fa_add_token))
|
||||||
}
|
}
|
||||||
@@ -133,13 +133,13 @@ fun TwoFaTokensScreen(
|
|||||||
Row {
|
Row {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { editingToken = item },
|
onClick = { editingToken = item },
|
||||||
enabled = uiState.isAvailable,
|
enabled = uiState.isAvailable && !uiState.isMutating,
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Edit, contentDescription = stringResource(R.string.edit))
|
Icon(Icons.Default.Edit, contentDescription = stringResource(R.string.edit))
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { viewModel.deleteToken(item.id) },
|
onClick = { viewModel.deleteToken(item.id) },
|
||||||
enabled = uiState.isAvailable,
|
enabled = uiState.isAvailable && !uiState.isMutating,
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.remove))
|
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.remove))
|
||||||
}
|
}
|
||||||
@@ -155,6 +155,7 @@ fun TwoFaTokensScreen(
|
|||||||
if (creating) {
|
if (creating) {
|
||||||
TwoFaTokenEditDialog(
|
TwoFaTokenEditDialog(
|
||||||
startValue = null,
|
startValue = null,
|
||||||
|
isBusy = uiState.isMutating,
|
||||||
onDismiss = { creating = false },
|
onDismiss = { creating = false },
|
||||||
onSave = { issuer, account, secret, notes ->
|
onSave = { issuer, account, secret, notes ->
|
||||||
creating = false
|
creating = false
|
||||||
@@ -172,6 +173,7 @@ fun TwoFaTokensScreen(
|
|||||||
editingToken?.let { token ->
|
editingToken?.let { token ->
|
||||||
TwoFaTokenEditDialog(
|
TwoFaTokenEditDialog(
|
||||||
startValue = token,
|
startValue = token,
|
||||||
|
isBusy = uiState.isMutating,
|
||||||
onDismiss = { editingToken = null },
|
onDismiss = { editingToken = null },
|
||||||
onSave = { issuer, account, secret, notes ->
|
onSave = { issuer, account, secret, notes ->
|
||||||
editingToken = null
|
editingToken = null
|
||||||
@@ -190,6 +192,7 @@ fun TwoFaTokensScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun TwoFaTokenEditDialog(
|
private fun TwoFaTokenEditDialog(
|
||||||
startValue: TwoFaTokenRecord?,
|
startValue: TwoFaTokenRecord?,
|
||||||
|
isBusy: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onSave: (String, String, String, String?) -> Unit,
|
onSave: (String, String, String, String?) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -235,14 +238,14 @@ private fun TwoFaTokenEditDialog(
|
|||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
enabled = issuer.isNotBlank() && account.isNotBlank() && secret.isNotBlank(),
|
enabled = !isBusy && issuer.isNotBlank() && account.isNotBlank() && secret.isNotBlank(),
|
||||||
onClick = { onSave(issuer, account, secret, notes.ifBlank { null }) },
|
onClick = { onSave(issuer, account, secret, notes.ifBlank { null }) },
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.save))
|
Text(stringResource(R.string.save))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismiss) {
|
TextButton(onClick = onDismiss, enabled = !isBusy) {
|
||||||
Text(stringResource(R.string.cancel))
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
|
|||||||
data class TwoFaTokensScreenState(
|
data class TwoFaTokensScreenState(
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val isAvailable: Boolean = false,
|
val isAvailable: Boolean = false,
|
||||||
|
val isMutating: Boolean = false,
|
||||||
val items: List<TwoFaTokenRecord> = emptyList(),
|
val items: List<TwoFaTokenRecord> = emptyList(),
|
||||||
val errorMessage: String? = null,
|
val errorMessage: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.twofa
|
|||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
|
import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
|
||||||
|
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
|
||||||
|
import com.github.nullptroma.wallenc.domain.tasks.TaskRunState
|
||||||
|
import com.github.nullptroma.wallenc.ui.R
|
||||||
import com.github.nullptroma.wallenc.ui.ViewModelBase
|
import com.github.nullptroma.wallenc.ui.ViewModelBase
|
||||||
|
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
|
||||||
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.requireStorageUuid
|
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.requireStorageUuid
|
||||||
import com.github.nullptroma.wallenc.usecases.FindStorageUseCase
|
import com.github.nullptroma.wallenc.usecases.FindStorageUseCase
|
||||||
import com.github.nullptroma.wallenc.usecases.ManageTwoFaTokensUseCase
|
import com.github.nullptroma.wallenc.usecases.ManageTwoFaTokensUseCase
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -17,6 +23,8 @@ class TwoFaTokensViewModel @Inject constructor(
|
|||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val findStorageUseCase: FindStorageUseCase,
|
private val findStorageUseCase: FindStorageUseCase,
|
||||||
private val manageTwoFaTokensUseCase: ManageTwoFaTokensUseCase,
|
private val manageTwoFaTokensUseCase: ManageTwoFaTokensUseCase,
|
||||||
|
private val taskOrchestrator: ITaskOrchestrator,
|
||||||
|
private val uiStrings: UiStringResolver,
|
||||||
) : ViewModelBase<TwoFaTokensScreenState>(TwoFaTokensScreenState()) {
|
) : ViewModelBase<TwoFaTokensScreenState>(TwoFaTokensScreenState()) {
|
||||||
|
|
||||||
private val storageUuid = savedStateHandle.requireStorageUuid()
|
private val storageUuid = savedStateHandle.requireStorageUuid()
|
||||||
@@ -40,10 +48,16 @@ class TwoFaTokensViewModel @Inject constructor(
|
|||||||
combine(
|
combine(
|
||||||
storage.isAvailable,
|
storage.isAvailable,
|
||||||
manageTwoFaTokensUseCase.observe(storage),
|
manageTwoFaTokensUseCase.observe(storage),
|
||||||
) { available, items ->
|
taskOrchestrator.pipelineState.map { pipe ->
|
||||||
|
pipe.tasks.any { t ->
|
||||||
|
t.busyStorageUuid == storage.uuid && isTaskActive(t.state)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { available, items, isMutating ->
|
||||||
state.value.copy(
|
state.value.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
isAvailable = available,
|
isAvailable = available,
|
||||||
|
isMutating = isMutating,
|
||||||
items = items,
|
items = items,
|
||||||
errorMessage = null,
|
errorMessage = null,
|
||||||
)
|
)
|
||||||
@@ -62,33 +76,50 @@ class TwoFaTokensViewModel @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val storage = findStorageUseCase.find(storageUuid) ?: return@launch
|
val storage = findStorageUseCase.find(storageUuid) ?: return@launch
|
||||||
if (existingId == null) {
|
taskOrchestrator.enqueue(
|
||||||
manageTwoFaTokensUseCase.create(
|
title = uiStrings(R.string.task_title_save_2fa_token),
|
||||||
storageInfo = storage,
|
dispatcher = Dispatchers.IO,
|
||||||
issuer = issuer,
|
busyStorageUuid = storage.uuid,
|
||||||
account = account,
|
work = { _ ->
|
||||||
secret = secret,
|
if (existingId == null) {
|
||||||
notes = notes,
|
manageTwoFaTokensUseCase.create(
|
||||||
)
|
storageInfo = storage,
|
||||||
} else {
|
issuer = issuer,
|
||||||
manageTwoFaTokensUseCase.update(
|
account = account,
|
||||||
storageInfo = storage,
|
secret = secret,
|
||||||
token = TwoFaTokenRecord(
|
notes = notes,
|
||||||
id = existingId,
|
)
|
||||||
issuer = issuer,
|
} else {
|
||||||
account = account,
|
manageTwoFaTokensUseCase.update(
|
||||||
secret = secret,
|
storageInfo = storage,
|
||||||
notes = notes,
|
token = TwoFaTokenRecord(
|
||||||
),
|
id = existingId,
|
||||||
)
|
issuer = issuer,
|
||||||
}
|
account = account,
|
||||||
|
secret = secret,
|
||||||
|
notes = notes,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteToken(id: String) {
|
fun deleteToken(id: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val storage = findStorageUseCase.find(storageUuid) ?: return@launch
|
val storage = findStorageUseCase.find(storageUuid) ?: return@launch
|
||||||
manageTwoFaTokensUseCase.delete(storage, id)
|
taskOrchestrator.enqueue(
|
||||||
|
title = uiStrings(R.string.task_title_delete_2fa_token),
|
||||||
|
dispatcher = Dispatchers.IO,
|
||||||
|
busyStorageUuid = storage.uuid,
|
||||||
|
work = { _ ->
|
||||||
|
manageTwoFaTokensUseCase.delete(storage, id)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isTaskActive(state: TaskRunState): Boolean =
|
||||||
|
state is TaskRunState.Queued || state is TaskRunState.Running
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,10 @@
|
|||||||
<string name="task_title_remove_remote_vault">Удаление удалённого хранилища</string>
|
<string name="task_title_remove_remote_vault">Удаление удалённого хранилища</string>
|
||||||
<string name="task_title_storage_sync">Синхронизация хранилищ</string>
|
<string name="task_title_storage_sync">Синхронизация хранилищ</string>
|
||||||
<string name="task_title_storage_sync_background">Фоновая синхронизация хранилищ</string>
|
<string name="task_title_storage_sync_background">Фоновая синхронизация хранилищ</string>
|
||||||
|
<string name="task_title_save_2fa_token">Сохранение 2FA токена</string>
|
||||||
|
<string name="task_title_delete_2fa_token">Удаление 2FA токена</string>
|
||||||
|
<string name="task_title_save_text_secret">Сохранение текстового секрета</string>
|
||||||
|
<string name="task_title_delete_text_secret">Удаление текстового секрета</string>
|
||||||
|
|
||||||
<string name="msg_encryption_enabled">Шифрование включено</string>
|
<string name="msg_encryption_enabled">Шифрование включено</string>
|
||||||
<string name="msg_storage_already_encrypted">Хранилище уже зашифровано</string>
|
<string name="msg_storage_already_encrypted">Хранилище уже зашифровано</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user