Исправлено много варнингов

This commit is contained in:
2026-05-17 19:27:15 +03:00
parent 3820a60d2c
commit 7d5fd1b634
17 changed files with 114 additions and 75 deletions

View File

@@ -7,6 +7,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<application
android:allowBackup="true"
@@ -33,12 +36,36 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="wallenc" android:host="main" />
<data android:scheme="wallenc" android:host="tasks" />
<data android:scheme="wallenc" android:host="settings" />
<data android:scheme="wallenc" />
<data android:host="main" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="wallenc" />
<data android:host="tasks" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="wallenc" />
<data android:host="settings" />
</intent-filter>
</activity>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<service
android:name=".tasks.TaskPipelineForegroundService"
android:exported="false"

View File

@@ -3,6 +3,7 @@ package com.github.nullptroma.wallenc.app.navigation
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.net.toUri
import com.github.nullptroma.wallenc.app.MainActivity
import com.github.nullptroma.wallenc.ui.navigation.WallencDeepLinks
@@ -39,5 +40,5 @@ object WallencExternalLaunch {
}
fun mainActivityViewIntentForTasks(context: Context): Intent =
mainActivityViewIntent(context, Uri.parse(WallencDeepLinks.TASKS_URI_PATTERN))
mainActivityViewIntent(context, WallencDeepLinks.TASKS_URI_PATTERN.toUri())
}

View File

@@ -8,7 +8,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.map
@@ -36,8 +35,8 @@ class StorageSyncBootstrap @Inject constructor(
}
val triggers = storages.flatMap { storage ->
listOf(
storage.accessor.filesUpdates.map { Unit },
storage.accessor.dirsUpdates.map { Unit },
storage.accessor.filesUpdates.map {},
storage.accessor.dirsUpdates.map {},
)
}
merge(*triggers.toTypedArray())

View File

@@ -196,11 +196,10 @@ class YandexDiskRepository(
}
return@withContext object : FilterInputStream(stream) {
override fun close() {
try {
`in`.close()
} finally {
resp.close()
`in`.use {
// Response must be closed after the wrapped stream.
}
resp.close()
}
}
}

View File

@@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.FileNotFoundException
import java.io.InputStream
import java.io.OutputStream
import java.time.Instant

View File

@@ -21,9 +21,9 @@ ksp = "2.3.7"
room = "2.8.4"
retrofit = "3.0.0"
okhttp = "5.3.2"
workRuntime = "2.10.0"
workRuntime = "2.11.2"
hiltWork = "1.3.0"
cameraX = "1.5.0"
cameraX = "1.6.1"
mlkitBarcode = "17.3.0"
javaOtp = "0.4.0"

View File

@@ -39,7 +39,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.style.TextAlign

View File

@@ -54,10 +54,8 @@ fun StorageHomeScreen(
}
Text(
text = if (uiState.storageName.isBlank()) {
text = uiState.storageName.ifBlank {
stringResource(R.string.storage_home_unnamed_storage)
} else {
uiState.storageName
},
style = MaterialTheme.typography.headlineSmall,
)

View File

@@ -1,6 +1,6 @@
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.twofa
import android.net.Uri
import androidx.core.net.toUri
data class ParsedOtpAuthToken(
val issuer: String,
@@ -12,7 +12,7 @@ data class ParsedOtpAuthToken(
)
fun parseOtpAuthTotpUri(raw: String): ParsedOtpAuthToken? {
val uri = runCatching { Uri.parse(raw.trim()) }.getOrNull() ?: return null
val uri = runCatching { raw.trim().toUri() }.getOrNull() ?: return null
if (!uri.scheme.equals("otpauth", ignoreCase = true)) return null
if (!uri.host.equals("totp", ignoreCase = true)) return null

View File

@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxHeight
@@ -44,18 +43,21 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.DropdownMenu
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.Alignment
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.AnnotatedString
@@ -68,6 +70,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.offset
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboard
import androidx.core.content.ContextCompat
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -77,6 +80,7 @@ import com.github.nullptroma.wallenc.ui.elements.QrScannerDialog
import com.github.nullptroma.wallenc.usecases.TwoFaCodeState
import com.github.nullptroma.wallenc.usecases.buildTwoFaCodeState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlin.math.roundToInt
import androidx.compose.ui.unit.IntOffset
@@ -86,7 +90,7 @@ fun TwoFaTokensScreen(
viewModel: TwoFaTokensViewModel = hiltViewModel(),
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
val clipboard = LocalClipboardManager.current
val clipboard = LocalClipboard.current
val nowMillis by produceState(initialValue = System.currentTimeMillis()) {
while (true) {
value = System.currentTimeMillis()
@@ -197,7 +201,7 @@ fun TwoFaTokensScreen(
.fillMaxWidth()
.padding(start = 14.dp, end = 14.dp, bottom = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = androidx.compose.ui.Alignment.Top,
verticalAlignment = Alignment.Top,
) {
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
@@ -242,7 +246,7 @@ fun TwoFaTokensScreen(
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = Icons.Default.ContentCopy,
@@ -326,15 +330,18 @@ private fun TwoFaTokenEditDialog(
var account by remember(startValue) { mutableStateOf(startValue?.account.orEmpty()) }
var secret by remember(startValue) { mutableStateOf(startValue?.secret.orEmpty()) }
var notes by remember(startValue) { mutableStateOf(startValue?.notes.orEmpty()) }
var digitsValue by remember(startValue) { mutableStateOf((startValue?.digits ?: 6).coerceIn(6, 8)) }
var periodSecondsValue by remember(startValue) { mutableStateOf((startValue?.periodSeconds ?: 30).coerceIn(10, 120)) }
var digitsValue by remember(startValue) { mutableIntStateOf((startValue?.digits ?: 6).coerceIn(6, 8)) }
var periodSecondsValue by remember(startValue) { mutableIntStateOf((startValue?.periodSeconds ?: 30).coerceIn(10, 120)) }
var algorithmValue by remember(startValue) { mutableStateOf((startValue?.algorithm ?: "SHA1").uppercase()) }
var algorithmExpanded by remember { mutableStateOf(false) }
var showScanner by remember { mutableStateOf(false) }
var scanError by remember { mutableStateOf<String?>(null) }
var permissionDenied by remember { mutableStateOf(false) }
val dialogScrollState = rememberScrollState()
var scrollContainerHeightPx by remember { mutableStateOf(0) }
var scrollContainerHeightPx by remember { mutableIntStateOf(0) }
var scrollProgress by remember { mutableFloatStateOf(0f) }
val density = LocalDensity.current
val scanInvalidText = stringResource(R.string.two_fa_scan_qr_invalid)
val cameraPermissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
) { granted ->
@@ -345,6 +352,12 @@ private fun TwoFaTokenEditDialog(
permissionDenied = true
}
}
LaunchedEffect(dialogScrollState) {
snapshotFlow { dialogScrollState.value to dialogScrollState.maxValue }
.collectLatest { (value, maxValue) ->
scrollProgress = if (maxValue == 0) 0f else value.toFloat() / maxValue.toFloat()
}
}
AlertDialog(
onDismissRequest = { if (!isBusy) onDismiss() },
@@ -512,12 +525,11 @@ private fun TwoFaTokenEditDialog(
val content = viewport + dialogScrollState.maxValue.toFloat()
val thumbHeightPx = (viewport * (viewport / content)).coerceAtLeast(24f)
val maxTravel = (viewport - thumbHeightPx).coerceAtLeast(0f)
val offsetY = if (dialogScrollState.maxValue == 0) 0f
else maxTravel * (dialogScrollState.value.toFloat() / dialogScrollState.maxValue.toFloat())
val offsetY = maxTravel * scrollProgress
Box(
modifier = Modifier
.offset { IntOffset(0, offsetY.roundToInt()) }
.size(width = 3.dp, height = (thumbHeightPx / context.resources.displayMetrics.density).dp)
.size(width = 3.dp, height = with(density) { thumbHeightPx.toDp() })
.background(thumbColor),
)
}
@@ -555,7 +567,7 @@ private fun TwoFaTokenEditDialog(
onScanned = { raw ->
val parsed = parseOtpAuthTotpUri(raw)
if (parsed == null) {
scanError = context.getString(R.string.two_fa_scan_qr_invalid)
scanError = scanInvalidText
return@QrScannerDialog
}
issuer = parsed.issuer

View File

@@ -107,7 +107,7 @@ abstract class AbstractVaultBrowserViewModel(
list.add(tree)
while (opened.containsKey(tree.value.uuid)) {
val child = opened.getValue(tree.value.uuid)
val nextTree = Tree<IStorageInfo>(child)
val nextTree = Tree(child)
tree.children = listOf(nextTree)
tree = nextTree
}

View File

@@ -25,6 +25,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@@ -47,19 +50,28 @@ fun VaultBrowserScreen(
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
val context = LocalContext.current
LaunchedEffect(Unit) {
var pendingNotification by remember { mutableStateOf<UserNotification?>(null) }
LaunchedEffect(viewModel) {
viewModel.userNotifications.collect { notification ->
val text = when (notification) {
pendingNotification = notification
}
}
val notificationText = when (val notification = pendingNotification) {
is UserNotification.TextRes -> {
if (notification.formatArgs.isEmpty()) {
context.getString(notification.id)
stringResource(notification.id)
} else {
context.getString(notification.id, *notification.formatArgs.toTypedArray())
stringResource(notification.id, *notification.formatArgs.toTypedArray())
}
}
is UserNotification.Plain -> notification.message
null -> null
}
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
LaunchedEffect(notificationText) {
if (notificationText != null) {
Toast.makeText(context, notificationText, Toast.LENGTH_SHORT).show()
pendingNotification = null
}
}

View File

@@ -3,7 +3,6 @@ package com.github.nullptroma.wallenc.ui.screens.sync
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -41,7 +40,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
@@ -59,19 +57,19 @@ fun StorageSyncScreen(
) {
val state by viewModel.state.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
val context = LocalContext.current
LaunchedEffect(state.userMessage) {
val um = state.userMessage ?: return@LaunchedEffect
val text = when (um) {
val userMessageText = when (val um = state.userMessage) {
is UserNotification.TextRes -> {
if (um.formatArgs.isEmpty()) {
context.getString(um.id)
stringResource(um.id)
} else {
context.getString(um.id, *um.formatArgs.toTypedArray())
stringResource(um.id, *um.formatArgs.toTypedArray())
}
}
is UserNotification.Plain -> um.message
null -> null
}
LaunchedEffect(state.userMessage) {
val text = userMessageText ?: return@LaunchedEffect
snackbarHostState.showSnackbar(text)
viewModel.consumeUserMessage()
}

View File

@@ -149,9 +149,7 @@ class StorageSyncViewModel @Inject constructor(
viewModelScope.launch {
withGroupMutationBusy {
val storage = storageByUuid[storageUuid]
if (storage == null) {
return@withGroupMutationBusy UserNotification.TextRes(R.string.sync_storage_not_in_vaults)
}
?: return@withGroupMutationBusy UserNotification.TextRes(R.string.sync_storage_not_in_vaults)
val isEncrypted = storage.metaInfo.value.encInfo != null
val result = groupsUseCase.addStorageToGroup(
groupId = groupId,
@@ -290,8 +288,7 @@ class StorageSyncViewModel @Inject constructor(
}
private fun vaultType(vault: DescribedVault?): String {
val descriptor = vault?.descriptor
return when (descriptor) {
return when (val descriptor = vault?.descriptor) {
is VaultDescriptor.LocalDevice -> uiStrings(R.string.vault_type_local_device)
is VaultDescriptor.LinkedRemote -> uiStrings(R.string.vault_type_remote, descriptor.brand.name)
null -> uiStrings(R.string.vault_type_unknown)
@@ -299,8 +296,7 @@ class StorageSyncViewModel @Inject constructor(
}
private fun vaultTitle(vault: DescribedVault?): String {
val descriptor = vault?.descriptor
return when (descriptor) {
return when (val descriptor = vault?.descriptor) {
is VaultDescriptor.LocalDevice -> uiStrings(R.string.vault_title_local)
is VaultDescriptor.LinkedRemote -> descriptor.accountDisplayName
null -> uiStrings(R.string.vault_title_unknown)

View File

@@ -19,7 +19,6 @@ import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import java.util.UUID
@@ -33,10 +32,10 @@ class ManageTextSecretsUseCase {
storage.accessor.filesUpdates
.filter { page ->
page.data.any { file ->
domainFilePathEquals(file.metaInfo.path, StorageDomainDataFiles.TEXT_SECRETS_FILE)
domainFilePathEquals(file.metaInfo.path)
}
}
.map { Unit },
.map {},
).map {
mutex.withLock { readAll(storage) }
}.distinctUntilChanged()
@@ -155,6 +154,6 @@ class ManageTextSecretsUseCase {
}
}
private fun domainFilePathEquals(left: String, right: String): Boolean =
left.trimStart('/') == right.trimStart('/')
private fun domainFilePathEquals(path: String): Boolean =
path.trimStart('/') == StorageDomainDataFiles.TEXT_SECRETS_FILE.trimStart('/')
}

View File

@@ -29,10 +29,10 @@ class ManageTwoFaTokensUseCase {
storage.accessor.filesUpdates
.filter { page ->
page.data.any { file ->
domainFilePathEquals(file.metaInfo.path, StorageDomainDataFiles.TWO_FA_TOKENS_FILE)
domainFilePathEquals(file.metaInfo.path)
}
}
.map { Unit },
.map {},
).map {
mutex.withLock { readAll(storage) }
}.distinctUntilChanged()
@@ -153,6 +153,6 @@ class ManageTwoFaTokensUseCase {
}
}
private fun domainFilePathEquals(left: String, right: String): Boolean =
left.trimStart('/') == right.trimStart('/')
private fun domainFilePathEquals(path: String): Boolean =
path.trimStart('/') == StorageDomainDataFiles.TWO_FA_TOKENS_FILE.trimStart('/')
}

View File

@@ -138,7 +138,7 @@ private class FakeStorage : IStorage {
private class FakeMetaInfo : IStorageMetaInfo {
override val encInfo: StorageEncryptionInfo? = null
override val name: String? = "Fake"
override val name: String = "Fake"
override val lastModified: Instant = Instant.now()
}