Исправлено множество предупреждений
This commit is contained in:
@@ -4,13 +4,11 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
|
||||
import androidx.lifecycle.viewmodel.compose.saveable
|
||||
import com.github.nullptroma.wallenc.ui.screens.ScreenRoute
|
||||
import com.github.nullptroma.wallenc.ui.screens.main.MainRoute
|
||||
import com.github.nullptroma.wallenc.ui.screens.main.screens.tasks.TaskPipelineRoute
|
||||
import com.github.nullptroma.wallenc.ui.screens.settings.SettingsRoute
|
||||
import com.github.nullptroma.wallenc.ui.screens.sync.StorageSyncRoute
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlin.collections.set
|
||||
|
||||
@HiltViewModel
|
||||
class WallencViewModel @javax.inject.Inject constructor(savedStateHandle: SavedStateHandle) :
|
||||
@@ -27,10 +25,4 @@ class WallencViewModel @javax.inject.Inject constructor(savedStateHandle: SavedS
|
||||
)
|
||||
}
|
||||
private set
|
||||
|
||||
fun updateRoute(qualifiedName: String, route: ScreenRoute) {
|
||||
routes = routes.toMutableMap().apply {
|
||||
this[qualifiedName] = route
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.github.nullptroma.wallenc.ui.elements
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.ViewGroup
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.ExperimentalGetImage
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
@@ -28,11 +28,9 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
@@ -41,8 +39,8 @@ import com.google.mlkit.vision.common.InputImage
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
@ExperimentalGetImage
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalGetImage::class)
|
||||
@Composable
|
||||
fun QrScannerDialog(
|
||||
onDismiss: () -> Unit,
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.github.nullptroma.wallenc.ui.elements.indication
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.IndicationNodeFactory
|
||||
import androidx.compose.foundation.interaction.InteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.node.DelegatableNode
|
||||
import androidx.compose.ui.node.DrawModifierNode
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private class ScaleNode(private val interactionSource: InteractionSource) :
|
||||
Modifier.Node(), DrawModifierNode {
|
||||
|
||||
var currentPressPosition: Offset = Offset.Zero
|
||||
val animatedScalePercent = Animatable(1f)
|
||||
|
||||
private suspend fun animateToPressed(pressPosition: Offset) {
|
||||
currentPressPosition = pressPosition
|
||||
animatedScalePercent.animateTo(0.9f, spring())
|
||||
}
|
||||
|
||||
private suspend fun animateToResting() {
|
||||
animatedScalePercent.animateTo(1f, spring())
|
||||
}
|
||||
|
||||
override fun onAttach() {
|
||||
coroutineScope.launch {
|
||||
interactionSource.interactions.collectLatest { interaction ->
|
||||
when (interaction) {
|
||||
is PressInteraction.Press -> animateToPressed(interaction.pressPosition)
|
||||
is PressInteraction.Release -> animateToResting()
|
||||
is PressInteraction.Cancel -> animateToResting()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun ContentDrawScope.draw() {
|
||||
scale(
|
||||
scale = animatedScalePercent.value,
|
||||
pivot = currentPressPosition
|
||||
) {
|
||||
this@draw.drawContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ScaleIndication : IndicationNodeFactory {
|
||||
override fun create(interactionSource: InteractionSource): DelegatableNode {
|
||||
return ScaleNode(interactionSource)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean = other === ScaleIndication
|
||||
override fun hashCode() = 100
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.github.nullptroma.wallenc.ui.extensions
|
||||
|
||||
import androidx.compose.ui.Modifier
|
||||
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.unit.Dp
|
||||
|
||||
fun Modifier.ignoreHorizontalParentPadding(horizontal: Dp): Modifier {
|
||||
return this.layout { measurable, constraints ->
|
||||
val overrideWidth = constraints.maxWidth + 2 * horizontal.roundToPx()
|
||||
val placeable = measurable.measure(constraints.copy(maxWidth = overrideWidth))
|
||||
layout(placeable.width, placeable.height) {
|
||||
placeable.place(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Modifier.ignoreVerticalParentPadding(vertical: Dp): Modifier {
|
||||
return this.layout { measurable, constraints ->
|
||||
val overrideHeight = constraints.maxHeight + 2 * vertical.roundToPx()
|
||||
val placeable = measurable.measure(constraints.copy(maxHeight = overrideHeight))
|
||||
layout(placeable.width, placeable.height) {
|
||||
placeable.place(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -6,50 +6,50 @@ import com.github.nullptroma.wallenc.usecases.AddStorageToSyncGroupResult
|
||||
import com.github.nullptroma.wallenc.vault.contract.VaultLinkFailure
|
||||
|
||||
fun WallencException.toUserNotification(): UserNotification.TextRes = when (this) {
|
||||
WallencException.Feature.StorageNotFound ->
|
||||
is WallencException.Feature.StorageNotFound ->
|
||||
UserNotification.TextRes(R.string.error_storage_not_found)
|
||||
WallencException.Feature.NeedsDecryptedView ->
|
||||
is WallencException.Feature.NeedsDecryptedView ->
|
||||
UserNotification.TextRes(R.string.error_storage_locked_view)
|
||||
WallencException.Feature.SecretNotFound ->
|
||||
is WallencException.Feature.SecretNotFound ->
|
||||
UserNotification.TextRes(R.string.error_secret_not_found)
|
||||
WallencException.Feature.StorageNotWritable ->
|
||||
is WallencException.Feature.StorageNotWritable ->
|
||||
UserNotification.TextRes(R.string.error_storage_not_writable)
|
||||
|
||||
WallencException.Storage.NotAvailable,
|
||||
WallencException.Storage.NotWritable,
|
||||
is WallencException.Storage.NotAvailable,
|
||||
is WallencException.Storage.NotWritable,
|
||||
->
|
||||
UserNotification.TextRes(R.string.error_storage_not_writable)
|
||||
WallencException.Storage.FileNotFound ->
|
||||
is WallencException.Storage.FileNotFound ->
|
||||
UserNotification.TextRes(R.string.error_file_not_found)
|
||||
WallencException.Storage.IncorrectKey ->
|
||||
is WallencException.Storage.IncorrectKey ->
|
||||
UserNotification.TextRes(R.string.error_incorrect_password)
|
||||
WallencException.Storage.NotEncrypted ->
|
||||
is WallencException.Storage.NotEncrypted ->
|
||||
UserNotification.TextRes(R.string.error_storage_not_encrypted)
|
||||
WallencException.Storage.EncInfoMissing ->
|
||||
is WallencException.Storage.EncInfoMissing ->
|
||||
UserNotification.TextRes(R.string.error_enc_info_missing)
|
||||
WallencException.Storage.DeleteRootForbidden ->
|
||||
is WallencException.Storage.DeleteRootForbidden ->
|
||||
UserNotification.TextRes(R.string.error_delete_root_forbidden)
|
||||
WallencException.Storage.NotAFile ->
|
||||
is WallencException.Storage.NotAFile ->
|
||||
UserNotification.TextRes(R.string.error_not_a_file)
|
||||
WallencException.Storage.NotADirectory ->
|
||||
is WallencException.Storage.NotADirectory ->
|
||||
UserNotification.TextRes(R.string.error_not_a_directory)
|
||||
WallencException.Storage.PathIsFile ->
|
||||
is WallencException.Storage.PathIsFile ->
|
||||
UserNotification.TextRes(R.string.error_path_is_file)
|
||||
WallencException.Storage.CannotWriteOverDirectory ->
|
||||
is WallencException.Storage.CannotWriteOverDirectory ->
|
||||
UserNotification.TextRes(R.string.error_cannot_write_over_directory)
|
||||
WallencException.Storage.UnexpectedState ->
|
||||
is WallencException.Storage.UnexpectedState ->
|
||||
UserNotification.TextRes(R.string.error_unexpected_state)
|
||||
is WallencException.Storage.IoFailed ->
|
||||
UserNotification.TextRes(R.string.error_network)
|
||||
|
||||
WallencException.Auth.Failed,
|
||||
WallencException.Auth.TokenMissing,
|
||||
is WallencException.Auth.Failed,
|
||||
is WallencException.Auth.TokenMissing,
|
||||
->
|
||||
UserNotification.TextRes(R.string.vault_link_error_auth)
|
||||
WallencException.Network.ResourceLocked ->
|
||||
is WallencException.Network.ResourceLocked ->
|
||||
UserNotification.TextRes(R.string.error_disk_resource_locked)
|
||||
WallencException.Network.OperationFailed,
|
||||
WallencException.Network.OperationTimedOut,
|
||||
is WallencException.Network.OperationFailed,
|
||||
is WallencException.Network.OperationTimedOut,
|
||||
is WallencException.Network.HttpFailed,
|
||||
is WallencException.Network.IoFailed,
|
||||
->
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
package com.github.nullptroma.wallenc.ui.resources
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLine
|
||||
|
||||
@Composable
|
||||
fun TaskLogLine.displayText(): String {
|
||||
val key = logKey
|
||||
if (key != null) {
|
||||
val context = LocalContext.current
|
||||
val resolver = UiStringResolver { id, args ->
|
||||
if (args.isEmpty()) {
|
||||
context.getString(id)
|
||||
} else {
|
||||
context.getString(id, *args)
|
||||
}
|
||||
}
|
||||
return key.resolve(resolver)
|
||||
return key.resolve(composableUiStringResolver())
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ fun TaskProgressLabel.resolve(resolver: UiStringResolver): String = when (this)
|
||||
TaskProgressLabel.SyncNoGroups -> resolver(R.string.sync_progress_no_groups)
|
||||
TaskProgressLabel.SyncStarted -> resolver(R.string.sync_progress_started)
|
||||
TaskProgressLabel.SyncCompleted -> resolver(R.string.sync_progress_completed)
|
||||
is TaskProgressLabel.SyncPreparing -> resolver(R.string.sync_progress_preparing, groupCount)
|
||||
is TaskProgressLabel.SyncPreparing ->
|
||||
resolver.plurals(R.plurals.sync_progress_preparing, groupCount, groupCount)
|
||||
|
||||
is TaskProgressLabel.SyncGroupPreparing -> resolver(R.string.sync_progress_group_preparing, groupId)
|
||||
is TaskProgressLabel.SyncGroupNotFound -> resolver(R.string.sync_progress_group_not_found, groupId)
|
||||
@@ -31,7 +32,7 @@ fun TaskProgressLabel.resolve(resolver: UiStringResolver): String = when (this)
|
||||
is TaskProgressLabel.SyncGroupNoJournalEntries ->
|
||||
resolver(R.string.sync_progress_group_no_entries, groupId)
|
||||
is TaskProgressLabel.SyncGroupProcessingEntries ->
|
||||
resolver(R.string.sync_progress_group_processing, groupId, count)
|
||||
resolver.plurals(R.plurals.sync_progress_group_processing, count, groupId, count)
|
||||
is TaskProgressLabel.SyncGroupEntryProgress ->
|
||||
resolver(R.string.sync_progress_group_entry, groupId, current, total)
|
||||
is TaskProgressLabel.SyncGroupCompleted ->
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
package com.github.nullptroma.wallenc.ui.resources
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.github.nullptroma.wallenc.domain.tasks.TaskProgressLabel
|
||||
|
||||
@Composable
|
||||
fun TaskProgressLabel.resolveText(): String {
|
||||
val context = LocalContext.current
|
||||
return resolve(
|
||||
UiStringResolver { id, args ->
|
||||
if (args.isEmpty()) {
|
||||
context.getString(id)
|
||||
} else {
|
||||
context.getString(id, *args)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fun TaskProgressLabel.resolveText(): String = resolve(composableUiStringResolver())
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.github.nullptroma.wallenc.ui.resources
|
||||
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
/** Разрешение Android-строк для код-домена (ViewModel, без Compose). */
|
||||
fun interface UiStringResolver {
|
||||
interface UiStringResolver {
|
||||
operator fun invoke(@StringRes id: Int, vararg formatArgs: Any): String
|
||||
|
||||
fun plurals(@PluralsRes id: Int, quantity: Int, vararg formatArgs: Any): String
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.nullptroma.wallenc.ui.resources
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun composableUiStringResolver(): UiStringResolver {
|
||||
val resources = LocalResources.current
|
||||
return remember(resources) {
|
||||
object : UiStringResolver {
|
||||
override fun invoke(id: Int, vararg formatArgs: Any): String =
|
||||
if (formatArgs.isEmpty()) {
|
||||
resources.getString(id)
|
||||
} else {
|
||||
resources.getString(id, *formatArgs)
|
||||
}
|
||||
|
||||
override fun plurals(id: Int, quantity: Int, vararg formatArgs: Any): String =
|
||||
if (formatArgs.isEmpty()) {
|
||||
resources.getQuantityString(id, quantity)
|
||||
} else {
|
||||
resources.getQuantityString(id, quantity, *formatArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun resolveString(@StringRes id: Int, vararg formatArgs: Any): String =
|
||||
if (formatArgs.isEmpty()) {
|
||||
stringResource(id)
|
||||
} else {
|
||||
stringResource(id, *formatArgs)
|
||||
}
|
||||
@@ -71,12 +71,6 @@ class MainViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateRoute(qualifiedName: String, route: ScreenRoute) {
|
||||
routes = routes.toMutableMap().apply {
|
||||
this[qualifiedName] = route
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapWorkStatus(
|
||||
fg: TaskForegroundUiState,
|
||||
pipe: PipelineState,
|
||||
|
||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
@@ -58,6 +59,7 @@ fun RemoteVaultsScreen(
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
|
||||
Box {
|
||||
Scaffold(
|
||||
@@ -215,9 +217,9 @@ fun RemoteVaultsScreen(
|
||||
is VaultLinkOutcome.Failed -> {
|
||||
val notification = outcome.reason.toUserNotification()
|
||||
val text = if (notification.formatArgs.isEmpty()) {
|
||||
context.getString(notification.id)
|
||||
resources.getString(notification.id)
|
||||
} else {
|
||||
context.getString(
|
||||
resources.getString(
|
||||
notification.id,
|
||||
*notification.formatArgs.toTypedArray(),
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Notes
|
||||
import androidx.compose.material.icons.outlined.Lock
|
||||
import androidx.compose.material.icons.outlined.Notes
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
|
||||
@@ -35,7 +35,7 @@ class StorageHomeViewModel @Inject constructor(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
storageUuid = storageUuid.toString(),
|
||||
errorNotification = WallencException.Feature.StorageNotFound.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.StorageNotFound().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -59,7 +59,7 @@ class StorageHomeViewModel @Inject constructor(
|
||||
textSecretsCount = secrets.size,
|
||||
canManageDomainData = canManageDomainData,
|
||||
errorNotification = if (isRawEncrypted) {
|
||||
WallencException.Feature.NeedsDecryptedView.toUserNotification()
|
||||
WallencException.Feature.NeedsDecryptedView().toUserNotification()
|
||||
} else {
|
||||
null
|
||||
},
|
||||
|
||||
@@ -48,7 +48,7 @@ class TextSecretDetailsViewModel @Inject constructor(
|
||||
updateState(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
errorNotification = WallencException.Feature.StorageNotFound.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.StorageNotFound().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -58,7 +58,7 @@ class TextSecretDetailsViewModel @Inject constructor(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
isAvailable = false,
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -80,7 +80,7 @@ class TextSecretDetailsViewModel @Inject constructor(
|
||||
isMutating = isMutating,
|
||||
secret = secret,
|
||||
errorNotification = if (secret == null) {
|
||||
WallencException.Feature.SecretNotFound.toUserNotification()
|
||||
WallencException.Feature.SecretNotFound().toUserNotification()
|
||||
} else {
|
||||
null
|
||||
},
|
||||
@@ -97,7 +97,7 @@ class TextSecretDetailsViewModel @Inject constructor(
|
||||
if (storage.metaInfo.value.encInfo != null && !storage.isVirtualStorage) {
|
||||
updateState(
|
||||
state.value.copy(
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
|
||||
@@ -52,7 +52,7 @@ class TextSecretEditViewModel @Inject constructor(
|
||||
updateState(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
errorNotification = WallencException.Feature.StorageNotFound.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.StorageNotFound().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -62,7 +62,7 @@ class TextSecretEditViewModel @Inject constructor(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
isAvailable = false,
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -100,7 +100,7 @@ class TextSecretEditViewModel @Inject constructor(
|
||||
if (storage.metaInfo.value.encInfo != null && !storage.isVirtualStorage) {
|
||||
updateState(
|
||||
state.value.copy(
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
|
||||
@@ -33,7 +33,7 @@ class TextSecretsViewModel @Inject constructor(
|
||||
updateState(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
errorNotification = WallencException.Feature.StorageNotFound.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.StorageNotFound().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -43,7 +43,7 @@ class TextSecretsViewModel @Inject constructor(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
isAvailable = false,
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
|
||||
@@ -28,11 +28,9 @@ import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.QrCodeScanner
|
||||
import androidx.compose.material.icons.outlined.Lock
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -80,7 +78,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import com.github.nullptroma.wallenc.ui.resources.resolveText
|
||||
import com.github.nullptroma.wallenc.ui.elements.QrScannerDialog
|
||||
import com.github.nullptroma.wallenc.usecases.TwoFaCodeState
|
||||
import com.github.nullptroma.wallenc.usecases.buildTwoFaCodeState
|
||||
@@ -256,7 +253,6 @@ fun TwoFaTokensScreen(
|
||||
isBusy = uiState.isMutating,
|
||||
onDismiss = { creating = false },
|
||||
onSave = { issuer, account, secret, notes, digits, periodSeconds, algorithm ->
|
||||
creating = false
|
||||
viewModel.saveToken(
|
||||
existingId = null,
|
||||
issuer = issuer,
|
||||
@@ -267,6 +263,7 @@ fun TwoFaTokensScreen(
|
||||
periodSeconds = periodSeconds,
|
||||
algorithm = algorithm,
|
||||
)
|
||||
creating = false
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -277,7 +274,6 @@ fun TwoFaTokensScreen(
|
||||
isBusy = uiState.isMutating,
|
||||
onDismiss = { editingToken = null },
|
||||
onSave = { issuer, account, secret, notes, digits, periodSeconds, algorithm ->
|
||||
editingToken = null
|
||||
viewModel.saveToken(
|
||||
existingId = token.id,
|
||||
issuer = issuer,
|
||||
@@ -288,6 +284,7 @@ fun TwoFaTokensScreen(
|
||||
periodSeconds = periodSeconds,
|
||||
algorithm = algorithm,
|
||||
)
|
||||
editingToken = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class TwoFaTokensViewModel @Inject constructor(
|
||||
updateState(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
errorNotification = WallencException.Feature.StorageNotFound.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.StorageNotFound().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -54,7 +54,7 @@ class TwoFaTokensViewModel @Inject constructor(
|
||||
state.value.copy(
|
||||
isLoading = false,
|
||||
isAvailable = false,
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -96,7 +96,7 @@ class TwoFaTokensViewModel @Inject constructor(
|
||||
if (storage.metaInfo.value.encInfo != null && !storage.isVirtualStorage) {
|
||||
updateState(
|
||||
state.value.copy(
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
@@ -144,7 +144,7 @@ class TwoFaTokensViewModel @Inject constructor(
|
||||
if (storage.metaInfo.value.encInfo != null && !storage.isVirtualStorage) {
|
||||
updateState(
|
||||
state.value.copy(
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView.toUserNotification(),
|
||||
errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
|
||||
@@ -4,8 +4,7 @@ import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.Tree
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IFile
|
||||
import com.github.nullptroma.wallenc.domain.errors.toWallencException
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
|
||||
@@ -14,19 +13,17 @@ import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
|
||||
import com.github.nullptroma.wallenc.domain.tasks.TaskProgressLabel
|
||||
import com.github.nullptroma.wallenc.domain.tasks.TaskRunState
|
||||
import com.github.nullptroma.wallenc.domain.tasks.VaultTaskStep
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import com.github.nullptroma.wallenc.ui.ViewModelBase
|
||||
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
|
||||
import com.github.nullptroma.wallenc.ui.resources.UserNotification
|
||||
import com.github.nullptroma.wallenc.ui.resources.toUserNotification
|
||||
import com.github.nullptroma.wallenc.usecases.GetOpenedStoragesUseCase
|
||||
import com.github.nullptroma.wallenc.usecases.ManageStoragesEncryptionUseCase
|
||||
import com.github.nullptroma.wallenc.usecases.ManageVaultUseCase
|
||||
import com.github.nullptroma.wallenc.usecases.RemoveStorageUseCase
|
||||
import com.github.nullptroma.wallenc.usecases.RenameStorageUseCase
|
||||
import com.github.nullptroma.wallenc.usecases.StorageFileManagementUseCase
|
||||
import com.github.nullptroma.wallenc.ui.R
|
||||
import com.github.nullptroma.wallenc.ui.ViewModelBase
|
||||
import com.github.nullptroma.wallenc.ui.extensions.toPrintable
|
||||
import com.github.nullptroma.wallenc.domain.errors.toWallencException
|
||||
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
|
||||
import com.github.nullptroma.wallenc.ui.resources.UserNotification
|
||||
import com.github.nullptroma.wallenc.ui.resources.toUserNotification
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -36,7 +33,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
/**
|
||||
* Общая логика дерева storages для локального и удалённого vault (presentation).
|
||||
@@ -153,42 +149,6 @@ abstract class AbstractVaultBrowserViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun printStorageInfoToLog(storage: IStorageInfo) {
|
||||
val id = storage.uuid
|
||||
if (isStorageTaskActive(id)) {
|
||||
notifyUser(R.string.vault_msg_storage_pipeline_busy)
|
||||
return
|
||||
}
|
||||
taskOrchestrator.enqueue(
|
||||
title = uiStrings(R.string.task_title_dump_storage_log),
|
||||
dispatcher = Dispatchers.IO,
|
||||
busyStorageUuid = id,
|
||||
work = { ctx ->
|
||||
ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.DumpStorageLog))
|
||||
storageFileManagementUseCase.setStorage(storage)
|
||||
ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_enumerating))
|
||||
val files: List<IFile>
|
||||
val dirs: List<IDirectory>
|
||||
val time = measureTimeMillis {
|
||||
files = storageFileManagementUseCase.getAllFiles()
|
||||
dirs = storageFileManagementUseCase.getAllDirs()
|
||||
}
|
||||
for (file in files) {
|
||||
logger.debug("Files", file.metaInfo.toString())
|
||||
}
|
||||
for (dir in dirs) {
|
||||
logger.debug("Dirs", dir.metaInfo.toString())
|
||||
}
|
||||
logger.debug("Time", "Time: $time ms")
|
||||
logger.debug("Storage", storage.toPrintable())
|
||||
ctx.log(
|
||||
TaskLogLevel.Info,
|
||||
uiStrings(R.string.task_log_enumerate_done, files.size, dirs.size, time),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun createStorage() {
|
||||
if (!state.value.addStorageFabEnabled) {
|
||||
logger.debug(TAG, "createStorage ignored (vault unavailable or FAB disabled)")
|
||||
|
||||
@@ -69,10 +69,9 @@ fun VaultBrowserScreen(
|
||||
null -> null
|
||||
}
|
||||
LaunchedEffect(notificationText) {
|
||||
if (notificationText != null) {
|
||||
Toast.makeText(context, notificationText, Toast.LENGTH_SHORT).show()
|
||||
pendingNotification = null
|
||||
}
|
||||
val text = notificationText ?: return@LaunchedEffect
|
||||
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
|
||||
pendingNotification = null
|
||||
}
|
||||
|
||||
val fabEnabled = uiState.addStorageFabEnabled
|
||||
|
||||
@@ -366,11 +366,9 @@ fun StorageSyncScreen(
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
val groupId = pendingRemoveGroupId
|
||||
val groupId = pendingRemoveGroupId ?: return@Button
|
||||
viewModel.removeGroup(groupId)
|
||||
pendingRemoveGroupId = null
|
||||
if (groupId != null) {
|
||||
viewModel.removeGroup(groupId)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.sync_confirm_delete))
|
||||
@@ -399,11 +397,9 @@ fun StorageSyncScreen(
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
val payload = pendingRemoveStorage
|
||||
val payload = pendingRemoveStorage ?: return@Button
|
||||
viewModel.removeStorageFromGroup(payload.first, payload.second)
|
||||
pendingRemoveStorage = null
|
||||
if (payload != null) {
|
||||
viewModel.removeStorageFromGroup(payload.first, payload.second)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.sync_confirm_delete))
|
||||
|
||||
15
ui/src/main/res/values-ru/plurals.xml
Normal file
15
ui/src/main/res/values-ru/plurals.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="sync_progress_preparing">
|
||||
<item quantity="one">Синхронизация: подготовка %d группы</item>
|
||||
<item quantity="few">Синхронизация: подготовка %d групп</item>
|
||||
<item quantity="many">Синхронизация: подготовка %d групп</item>
|
||||
<item quantity="other">Синхронизация: подготовка %d групп</item>
|
||||
</plurals>
|
||||
<plurals name="sync_progress_group_processing">
|
||||
<item quantity="one">Синхронизация: группа «%1$s» — обработка %2$d записи</item>
|
||||
<item quantity="few">Синхронизация: группа «%1$s» — обработка %2$d записей</item>
|
||||
<item quantity="many">Синхронизация: группа «%1$s» — обработка %2$d записей</item>
|
||||
<item quantity="other">Синхронизация: группа «%1$s» — обработка %2$d записей</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -15,13 +15,10 @@
|
||||
<string name="sync_progress_section_title">Синхронизация хранилищ</string>
|
||||
<string name="sync_groups_busy_section_title">Сохранение групп синхронизации</string>
|
||||
<string name="sync_run_now">Запустить синхронизацию</string>
|
||||
<string name="sync_cd_run_now">Запустить синхронизацию сейчас</string>
|
||||
<string name="sync_refresh">Обновить</string>
|
||||
<string name="sync_add_storage">Добавить хранилище в группу</string>
|
||||
<string name="sync_remove_group">Удалить группу</string>
|
||||
<string name="sync_group_empty">В группе нет хранилищ</string>
|
||||
<string name="sync_remove_storage">Убрать хранилище из группы</string>
|
||||
<string name="sync_picker_back">Назад</string>
|
||||
<string name="sync_cd_picker_back">Закрыть выбор хранилища</string>
|
||||
<string name="sync_picker_title">Выбор хранилища для %1$s</string>
|
||||
<string name="sync_picker_add">Добавить</string>
|
||||
@@ -31,7 +28,6 @@
|
||||
<string name="sync_picker_expand">Развернуть</string>
|
||||
<string name="sync_picker_collapse">Свернуть</string>
|
||||
<string name="sync_fab_create_group_cd">Создать группу синхронизации</string>
|
||||
<string name="sync_group_mixed_encryption_warning">В группе разное шифрование: задайте единый режим</string>
|
||||
<string name="sync_group_incompatible_warning">Несовместимые хранилища в группе: %1$d</string>
|
||||
<string name="sync_group_policy_line">Политика шифрования группы: %1$s</string>
|
||||
<string name="sync_group_policy_unset">Не определена (группа пуста)</string>
|
||||
@@ -51,8 +47,6 @@
|
||||
<string name="sync_msg_only_plain_storage_allowed">В группы синхронизации можно добавлять только незашифрованные хранилища</string>
|
||||
<string name="sync_msg_storage_encryption_key_required">Для зашифрованного хранилища нужно знать пароль (откройте его перед добавлением)</string>
|
||||
<string name="sync_msg_storage_incompatible_encryption">Хранилище не совместимо с политикой шифрования группы</string>
|
||||
<string name="sync_msg_virtual_storage_not_supported">Нельзя добавлять открытое виртуальное хранилище: синхронизация работает с исходными raw storage</string>
|
||||
<string name="sync_msg_task_enqueued">Задача синхронизации поставлена в очередь</string>
|
||||
<string name="sync_msg_sync_already_running">Синхронизация уже выполняется</string>
|
||||
<string name="sync_msg_blocked_during_sync">Дождитесь окончания синхронизации</string>
|
||||
<string name="sync_encryption_unknown">Неизвестно</string>
|
||||
@@ -208,7 +202,6 @@
|
||||
<string name="enc_status_not_encrypted">Не зашифровано</string>
|
||||
<string name="enc_status_encrypted_open">Зашифровано (открыто)</string>
|
||||
<string name="enc_status_encrypted">Зашифровано</string>
|
||||
<string name="text_edit_screen_title">Текст</string>
|
||||
<string name="text_edit_screen_placeholder">Содержимое: %1$s</string>
|
||||
<string name="storage_home_unnamed_storage">Storage</string>
|
||||
<string name="storage_home_status_line">Статус: %1$s, %2$s</string>
|
||||
@@ -217,10 +210,8 @@
|
||||
<string name="storage_home_status_encrypted">зашифровано</string>
|
||||
<string name="storage_home_status_not_encrypted">не зашифровано</string>
|
||||
<string name="storage_home_two_fa_title">2FA токены (%1$d)</string>
|
||||
<string name="storage_home_open_two_fa">Открыть 2FA</string>
|
||||
<string name="storage_home_two_fa_subtitle">Коды и секреты двухфакторной аутентификации</string>
|
||||
<string name="storage_home_text_secrets_title">Текстовые секреты (%1$d)</string>
|
||||
<string name="storage_home_open_text_secrets">Открыть текстовые секреты</string>
|
||||
<string name="storage_home_text_secrets_subtitle">Заметки, токены и произвольные пары ключ-значение</string>
|
||||
<string name="storage_home_future_sections">Скоро здесь появятся Files, Media и другие типы данных.</string>
|
||||
<string name="two_fa_add_token">Добавить токен</string>
|
||||
@@ -231,17 +222,13 @@
|
||||
<string name="two_fa_field_account">Аккаунт</string>
|
||||
<string name="two_fa_field_secret">Секрет</string>
|
||||
<string name="two_fa_field_notes_optional">Заметка (опционально)</string>
|
||||
<string name="two_fa_field_digits">Количество цифр кода (обычно 6 или 8)</string>
|
||||
<string name="two_fa_field_period_seconds">Период обновления в секундах (обычно 30)</string>
|
||||
<string name="two_fa_field_algorithm">Алгоритм (SHA1, SHA256, SHA512)</string>
|
||||
<string name="two_fa_field_digits_value">Количество цифр: %1$d</string>
|
||||
<string name="two_fa_field_period_seconds_value">Период обновления: %1$d с</string>
|
||||
<string name="two_fa_code_unavailable">------</string>
|
||||
<string name="two_fa_code_refresh_in">Обновление через %1$d с</string>
|
||||
<string name="two_fa_code_refresh_label">Обновление через</string>
|
||||
<string name="two_fa_code_refresh_seconds">%1$d с</string>
|
||||
<string name="two_fa_code_invalid_secret">Неверный секрет или формат</string>
|
||||
<string name="two_fa_copy_code_hint">Нажмите, чтобы скопировать код</string>
|
||||
<string name="two_fa_scan_qr_action">Сканировать QR</string>
|
||||
<string name="two_fa_scan_qr_title">Сканирование QR-кода TOTP</string>
|
||||
<string name="two_fa_scan_qr_invalid">QR-код не содержит валидный otpauth://totp URI</string>
|
||||
@@ -259,9 +246,7 @@
|
||||
<string name="text_secret_copy_value">Скопировать значение</string>
|
||||
<string name="save">Сохранить</string>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="open">Открыть</string>
|
||||
<string name="edit">Редактировать</string>
|
||||
<string name="common_unknown">Неизвестно</string>
|
||||
<string name="settings_language_section">Язык</string>
|
||||
<string name="settings_language_system">Как в системе</string>
|
||||
<string name="settings_language_english">English</string>
|
||||
@@ -269,7 +254,6 @@
|
||||
<string name="task_pipeline_test_elapsed">Прошло: %1$d с / %2$d с</string>
|
||||
<string name="text_secret_clipboard_fallback_label">значение</string>
|
||||
<string name="sync_progress_no_groups">Синхронизация: группы не настроены</string>
|
||||
<string name="sync_progress_preparing">Синхронизация: подготовка %1$d групп</string>
|
||||
<string name="sync_progress_started">Синхронизация: запущена</string>
|
||||
<string name="sync_progress_completed">Синхронизация: завершена</string>
|
||||
<string name="sync_progress_group_preparing">Синхронизация: группа «%1$s» — подготовка</string>
|
||||
@@ -283,7 +267,6 @@
|
||||
<string name="sync_progress_group_cancelled">Синхронизация: группа «%1$s» отменена новым запуском</string>
|
||||
<string name="sync_progress_group_journal">Синхронизация: группа «%1$s» — журнал %2$d/%3$d</string>
|
||||
<string name="sync_progress_group_no_entries">Синхронизация: группа «%1$s» — нет записей в журнале</string>
|
||||
<string name="sync_progress_group_processing">Синхронизация: группа «%1$s» — обработка %2$d записей</string>
|
||||
<string name="sync_progress_group_entry">Синхронизация: группа «%1$s» — запись %2$d/%3$d</string>
|
||||
<string name="sync_progress_group_completed">Синхронизация: группа «%1$s» завершена</string>
|
||||
<string name="sync_progress_group_renewing_locks">Синхронизация: группа «%1$s» — продление блокировок</string>
|
||||
@@ -293,7 +276,6 @@
|
||||
<string name="task_log_sync_finished">Синхронизация хранилищ завершена</string>
|
||||
<string name="task_log_sync_failed">Синхронизация не удалась: %1$s</string>
|
||||
<string name="task_log_enumerating">Перечисление файлов и папок…</string>
|
||||
<string name="task_log_enumerate_done">Готово: %1$d файлов, %2$d папок за %3$d мс (подробности в журнале приложения)</string>
|
||||
<string name="task_log_creating_storage">Создание хранилища…</string>
|
||||
<string name="task_log_storage_created">Хранилище создано</string>
|
||||
<string name="task_log_checking_storage">Проверка хранилища…</string>
|
||||
|
||||
11
ui/src/main/res/values/plurals.xml
Normal file
11
ui/src/main/res/values/plurals.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="sync_progress_preparing">
|
||||
<item quantity="one">Storage sync: preparing %d group</item>
|
||||
<item quantity="other">Storage sync: preparing %d groups</item>
|
||||
</plurals>
|
||||
<plurals name="sync_progress_group_processing">
|
||||
<item quantity="one">Storage sync: group "%1$s" processing %2$d entry</item>
|
||||
<item quantity="other">Storage sync: group "%1$s" processing %2$d entries</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -15,13 +15,10 @@
|
||||
<string name="sync_progress_section_title">Storage sync</string>
|
||||
<string name="sync_groups_busy_section_title">Saving sync groups</string>
|
||||
<string name="sync_run_now">Run sync now</string>
|
||||
<string name="sync_cd_run_now">Run sync now</string>
|
||||
<string name="sync_refresh">Refresh</string>
|
||||
<string name="sync_add_storage">Add storage to group</string>
|
||||
<string name="sync_remove_group">Remove group</string>
|
||||
<string name="sync_group_empty">No storages in this group</string>
|
||||
<string name="sync_remove_storage">Remove storage from group</string>
|
||||
<string name="sync_picker_back">Back</string>
|
||||
<string name="sync_cd_picker_back">Close storage picker</string>
|
||||
<string name="sync_picker_title">Pick storage for %1$s</string>
|
||||
<string name="sync_picker_add">Add</string>
|
||||
@@ -31,7 +28,6 @@
|
||||
<string name="sync_picker_expand">Expand</string>
|
||||
<string name="sync_picker_collapse">Collapse</string>
|
||||
<string name="sync_fab_create_group_cd">Create sync group</string>
|
||||
<string name="sync_group_mixed_encryption_warning">Mixed encryption in group: set a single mode</string>
|
||||
<string name="sync_group_incompatible_warning">Incompatible storages in group: %1$d</string>
|
||||
<string name="sync_group_policy_line">Group encryption policy: %1$s</string>
|
||||
<string name="sync_group_policy_unset">Not set (group is empty)</string>
|
||||
@@ -51,8 +47,6 @@
|
||||
<string name="sync_msg_only_plain_storage_allowed">Only unencrypted storages can be added to sync groups</string>
|
||||
<string name="sync_msg_storage_encryption_key_required">Encrypted storage requires the password (open it before adding)</string>
|
||||
<string name="sync_msg_storage_incompatible_encryption">Storage is not compatible with the group encryption policy</string>
|
||||
<string name="sync_msg_virtual_storage_not_supported">Cannot add an open virtual storage: sync works with raw storages</string>
|
||||
<string name="sync_msg_task_enqueued">Sync task queued</string>
|
||||
<string name="sync_msg_sync_already_running">Sync is already running</string>
|
||||
<string name="sync_msg_blocked_during_sync">Wait for sync to finish</string>
|
||||
<string name="sync_encryption_unknown">Unknown</string>
|
||||
@@ -208,7 +202,6 @@
|
||||
<string name="enc_status_not_encrypted">Not encrypted</string>
|
||||
<string name="enc_status_encrypted_open">Encrypted (open)</string>
|
||||
<string name="enc_status_encrypted">Encrypted</string>
|
||||
<string name="text_edit_screen_title">Text</string>
|
||||
<string name="text_edit_screen_placeholder">Content: %1$s</string>
|
||||
<string name="storage_home_unnamed_storage">Storage</string>
|
||||
<string name="storage_home_status_line">Status: %1$s, %2$s</string>
|
||||
@@ -217,10 +210,8 @@
|
||||
<string name="storage_home_status_encrypted">encrypted</string>
|
||||
<string name="storage_home_status_not_encrypted">not encrypted</string>
|
||||
<string name="storage_home_two_fa_title">2FA tokens (%1$d)</string>
|
||||
<string name="storage_home_open_two_fa">Open 2FA</string>
|
||||
<string name="storage_home_two_fa_subtitle">Two-factor authentication codes and secrets</string>
|
||||
<string name="storage_home_text_secrets_title">Text secrets (%1$d)</string>
|
||||
<string name="storage_home_open_text_secrets">Open text secrets</string>
|
||||
<string name="storage_home_text_secrets_subtitle">Notes, tokens, and arbitrary key-value pairs</string>
|
||||
<string name="storage_home_future_sections">Files, Media, and more will appear here soon.</string>
|
||||
<string name="two_fa_add_token">Add token</string>
|
||||
@@ -231,17 +222,13 @@
|
||||
<string name="two_fa_field_account">Account</string>
|
||||
<string name="two_fa_field_secret">Secret</string>
|
||||
<string name="two_fa_field_notes_optional">Note (optional)</string>
|
||||
<string name="two_fa_field_digits">Code digits (usually 6 or 8)</string>
|
||||
<string name="two_fa_field_period_seconds">Refresh period in seconds (usually 30)</string>
|
||||
<string name="two_fa_field_algorithm">Algorithm (SHA1, SHA256, SHA512)</string>
|
||||
<string name="two_fa_field_digits_value">Digits: %1$d</string>
|
||||
<string name="two_fa_field_period_seconds_value">Refresh period: %1$d s</string>
|
||||
<string name="two_fa_code_unavailable">------</string>
|
||||
<string name="two_fa_code_refresh_in">Refresh in %1$d s</string>
|
||||
<string name="two_fa_code_refresh_label">Refresh in</string>
|
||||
<string name="two_fa_code_refresh_seconds">%1$d s</string>
|
||||
<string name="two_fa_code_invalid_secret">Invalid secret or format</string>
|
||||
<string name="two_fa_copy_code_hint">Tap to copy code</string>
|
||||
<string name="two_fa_scan_qr_action">Scan QR</string>
|
||||
<string name="two_fa_scan_qr_title">Scan TOTP QR code</string>
|
||||
<string name="two_fa_scan_qr_invalid">QR code does not contain a valid otpauth://totp URI</string>
|
||||
@@ -259,9 +246,7 @@
|
||||
<string name="text_secret_copy_value">Copy value</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="open">Open</string>
|
||||
<string name="edit">Edit</string>
|
||||
<string name="common_unknown">Unknown</string>
|
||||
<string name="settings_language_section">Language</string>
|
||||
<string name="settings_language_system">System default</string>
|
||||
<string name="settings_language_english">English</string>
|
||||
@@ -269,7 +254,6 @@
|
||||
<string name="task_pipeline_test_elapsed">Elapsed: %1$d s / %2$d s</string>
|
||||
<string name="text_secret_clipboard_fallback_label">value</string>
|
||||
<string name="sync_progress_no_groups">Storage sync: no groups configured</string>
|
||||
<string name="sync_progress_preparing">Storage sync: preparing %1$d groups</string>
|
||||
<string name="sync_progress_started">Storage sync: started</string>
|
||||
<string name="sync_progress_completed">Storage sync: completed</string>
|
||||
<string name="sync_progress_group_preparing">Storage sync: group "%1$s" preparing</string>
|
||||
@@ -283,7 +267,6 @@
|
||||
<string name="sync_progress_group_cancelled">Storage sync: group "%1$s" cancelled by newer run</string>
|
||||
<string name="sync_progress_group_journal">Storage sync: group "%1$s" journal %2$d/%3$d</string>
|
||||
<string name="sync_progress_group_no_entries">Storage sync: group "%1$s" no journal entries</string>
|
||||
<string name="sync_progress_group_processing">Storage sync: group "%1$s" processing %2$d entries</string>
|
||||
<string name="sync_progress_group_entry">Storage sync: group "%1$s" entry %2$d/%3$d</string>
|
||||
<string name="sync_progress_group_completed">Storage sync: group "%1$s" completed</string>
|
||||
<string name="sync_progress_group_renewing_locks">Storage sync: group "%1$s" renewing locks</string>
|
||||
@@ -293,7 +276,6 @@
|
||||
<string name="task_log_sync_finished">Storage sync finished</string>
|
||||
<string name="task_log_sync_failed">Storage sync failed: %1$s</string>
|
||||
<string name="task_log_enumerating">Enumerating files and directories…</string>
|
||||
<string name="task_log_enumerate_done">Done: %1$d files, %2$d dirs in %3$d ms (see app log for lines)</string>
|
||||
<string name="task_log_creating_storage">Creating storage…</string>
|
||||
<string name="task_log_storage_created">Storage created</string>
|
||||
<string name="task_log_checking_storage">Checking storage…</string>
|
||||
|
||||
Reference in New Issue
Block a user