Исправлено много варнингов
This commit is contained in:
@@ -7,6 +7,9 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.camera"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -33,12 +36,36 @@
|
|||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="wallenc" android:host="main" />
|
<data android:scheme="wallenc" />
|
||||||
<data android:scheme="wallenc" android:host="tasks" />
|
<data android:host="main" />
|
||||||
<data android:scheme="wallenc" android:host="settings" />
|
</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>
|
</intent-filter>
|
||||||
</activity>
|
</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
|
<service
|
||||||
android:name=".tasks.TaskPipelineForegroundService"
|
android:name=".tasks.TaskPipelineForegroundService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.github.nullptroma.wallenc.app.navigation
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.core.net.toUri
|
||||||
import com.github.nullptroma.wallenc.app.MainActivity
|
import com.github.nullptroma.wallenc.app.MainActivity
|
||||||
import com.github.nullptroma.wallenc.ui.navigation.WallencDeepLinks
|
import com.github.nullptroma.wallenc.ui.navigation.WallencDeepLinks
|
||||||
|
|
||||||
@@ -39,5 +40,5 @@ object WallencExternalLaunch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun mainActivityViewIntentForTasks(context: Context): Intent =
|
fun mainActivityViewIntentForTasks(context: Context): Intent =
|
||||||
mainActivityViewIntent(context, Uri.parse(WallencDeepLinks.TASKS_URI_PATTERN))
|
mainActivityViewIntent(context, WallencDeepLinks.TASKS_URI_PATTERN.toUri())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -36,8 +35,8 @@ class StorageSyncBootstrap @Inject constructor(
|
|||||||
}
|
}
|
||||||
val triggers = storages.flatMap { storage ->
|
val triggers = storages.flatMap { storage ->
|
||||||
listOf(
|
listOf(
|
||||||
storage.accessor.filesUpdates.map { Unit },
|
storage.accessor.filesUpdates.map {},
|
||||||
storage.accessor.dirsUpdates.map { Unit },
|
storage.accessor.dirsUpdates.map {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
merge(*triggers.toTypedArray())
|
merge(*triggers.toTypedArray())
|
||||||
|
|||||||
@@ -196,11 +196,10 @@ class YandexDiskRepository(
|
|||||||
}
|
}
|
||||||
return@withContext object : FilterInputStream(stream) {
|
return@withContext object : FilterInputStream(stream) {
|
||||||
override fun close() {
|
override fun close() {
|
||||||
try {
|
`in`.use {
|
||||||
`in`.close()
|
// Response must be closed after the wrapped stream.
|
||||||
} finally {
|
|
||||||
resp.close()
|
|
||||||
}
|
}
|
||||||
|
resp.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ ksp = "2.3.7"
|
|||||||
room = "2.8.4"
|
room = "2.8.4"
|
||||||
retrofit = "3.0.0"
|
retrofit = "3.0.0"
|
||||||
okhttp = "5.3.2"
|
okhttp = "5.3.2"
|
||||||
workRuntime = "2.10.0"
|
workRuntime = "2.11.2"
|
||||||
hiltWork = "1.3.0"
|
hiltWork = "1.3.0"
|
||||||
cameraX = "1.5.0"
|
cameraX = "1.6.1"
|
||||||
mlkitBarcode = "17.3.0"
|
mlkitBarcode = "17.3.0"
|
||||||
javaOtp = "0.4.0"
|
javaOtp = "0.4.0"
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.PlatformTextStyle
|
import androidx.compose.ui.text.PlatformTextStyle
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|||||||
@@ -54,10 +54,8 @@ fun StorageHomeScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = if (uiState.storageName.isBlank()) {
|
text = uiState.storageName.ifBlank {
|
||||||
stringResource(R.string.storage_home_unnamed_storage)
|
stringResource(R.string.storage_home_unnamed_storage)
|
||||||
} else {
|
|
||||||
uiState.storageName
|
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.twofa
|
package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.twofa
|
||||||
|
|
||||||
import android.net.Uri
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
data class ParsedOtpAuthToken(
|
data class ParsedOtpAuthToken(
|
||||||
val issuer: String,
|
val issuer: String,
|
||||||
@@ -12,7 +12,7 @@ data class ParsedOtpAuthToken(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun parseOtpAuthTotpUri(raw: String): 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.scheme.equals("otpauth", ignoreCase = true)) return null
|
||||||
if (!uri.host.equals("totp", ignoreCase = true)) return null
|
if (!uri.host.equals("totp", ignoreCase = true)) return null
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
@@ -44,18 +43,21 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
@@ -68,6 +70,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalClipboard
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
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.TwoFaCodeState
|
||||||
import com.github.nullptroma.wallenc.usecases.buildTwoFaCodeState
|
import com.github.nullptroma.wallenc.usecases.buildTwoFaCodeState
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
|
||||||
@@ -86,7 +90,7 @@ fun TwoFaTokensScreen(
|
|||||||
viewModel: TwoFaTokensViewModel = hiltViewModel(),
|
viewModel: TwoFaTokensViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboard.current
|
||||||
val nowMillis by produceState(initialValue = System.currentTimeMillis()) {
|
val nowMillis by produceState(initialValue = System.currentTimeMillis()) {
|
||||||
while (true) {
|
while (true) {
|
||||||
value = System.currentTimeMillis()
|
value = System.currentTimeMillis()
|
||||||
@@ -197,7 +201,7 @@ fun TwoFaTokensScreen(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 14.dp, end = 14.dp, bottom = 10.dp),
|
.padding(start = 14.dp, end = 14.dp, bottom = 10.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = androidx.compose.ui.Alignment.Top,
|
verticalAlignment = Alignment.Top,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
@@ -242,7 +246,7 @@ fun TwoFaTokensScreen(
|
|||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ContentCopy,
|
imageVector = Icons.Default.ContentCopy,
|
||||||
@@ -326,15 +330,18 @@ private fun TwoFaTokenEditDialog(
|
|||||||
var account by remember(startValue) { mutableStateOf(startValue?.account.orEmpty()) }
|
var account by remember(startValue) { mutableStateOf(startValue?.account.orEmpty()) }
|
||||||
var secret by remember(startValue) { mutableStateOf(startValue?.secret.orEmpty()) }
|
var secret by remember(startValue) { mutableStateOf(startValue?.secret.orEmpty()) }
|
||||||
var notes by remember(startValue) { mutableStateOf(startValue?.notes.orEmpty()) }
|
var notes by remember(startValue) { mutableStateOf(startValue?.notes.orEmpty()) }
|
||||||
var digitsValue by remember(startValue) { mutableStateOf((startValue?.digits ?: 6).coerceIn(6, 8)) }
|
var digitsValue by remember(startValue) { mutableIntStateOf((startValue?.digits ?: 6).coerceIn(6, 8)) }
|
||||||
var periodSecondsValue by remember(startValue) { mutableStateOf((startValue?.periodSeconds ?: 30).coerceIn(10, 120)) }
|
var periodSecondsValue by remember(startValue) { mutableIntStateOf((startValue?.periodSeconds ?: 30).coerceIn(10, 120)) }
|
||||||
var algorithmValue by remember(startValue) { mutableStateOf((startValue?.algorithm ?: "SHA1").uppercase()) }
|
var algorithmValue by remember(startValue) { mutableStateOf((startValue?.algorithm ?: "SHA1").uppercase()) }
|
||||||
var algorithmExpanded by remember { mutableStateOf(false) }
|
var algorithmExpanded by remember { mutableStateOf(false) }
|
||||||
var showScanner by remember { mutableStateOf(false) }
|
var showScanner by remember { mutableStateOf(false) }
|
||||||
var scanError by remember { mutableStateOf<String?>(null) }
|
var scanError by remember { mutableStateOf<String?>(null) }
|
||||||
var permissionDenied by remember { mutableStateOf(false) }
|
var permissionDenied by remember { mutableStateOf(false) }
|
||||||
val dialogScrollState = rememberScrollState()
|
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(
|
val cameraPermissionLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.RequestPermission(),
|
contract = ActivityResultContracts.RequestPermission(),
|
||||||
) { granted ->
|
) { granted ->
|
||||||
@@ -345,6 +352,12 @@ private fun TwoFaTokenEditDialog(
|
|||||||
permissionDenied = true
|
permissionDenied = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(dialogScrollState) {
|
||||||
|
snapshotFlow { dialogScrollState.value to dialogScrollState.maxValue }
|
||||||
|
.collectLatest { (value, maxValue) ->
|
||||||
|
scrollProgress = if (maxValue == 0) 0f else value.toFloat() / maxValue.toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { if (!isBusy) onDismiss() },
|
onDismissRequest = { if (!isBusy) onDismiss() },
|
||||||
@@ -512,12 +525,11 @@ private fun TwoFaTokenEditDialog(
|
|||||||
val content = viewport + dialogScrollState.maxValue.toFloat()
|
val content = viewport + dialogScrollState.maxValue.toFloat()
|
||||||
val thumbHeightPx = (viewport * (viewport / content)).coerceAtLeast(24f)
|
val thumbHeightPx = (viewport * (viewport / content)).coerceAtLeast(24f)
|
||||||
val maxTravel = (viewport - thumbHeightPx).coerceAtLeast(0f)
|
val maxTravel = (viewport - thumbHeightPx).coerceAtLeast(0f)
|
||||||
val offsetY = if (dialogScrollState.maxValue == 0) 0f
|
val offsetY = maxTravel * scrollProgress
|
||||||
else maxTravel * (dialogScrollState.value.toFloat() / dialogScrollState.maxValue.toFloat())
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.offset { IntOffset(0, offsetY.roundToInt()) }
|
.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),
|
.background(thumbColor),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -555,7 +567,7 @@ private fun TwoFaTokenEditDialog(
|
|||||||
onScanned = { raw ->
|
onScanned = { raw ->
|
||||||
val parsed = parseOtpAuthTotpUri(raw)
|
val parsed = parseOtpAuthTotpUri(raw)
|
||||||
if (parsed == null) {
|
if (parsed == null) {
|
||||||
scanError = context.getString(R.string.two_fa_scan_qr_invalid)
|
scanError = scanInvalidText
|
||||||
return@QrScannerDialog
|
return@QrScannerDialog
|
||||||
}
|
}
|
||||||
issuer = parsed.issuer
|
issuer = parsed.issuer
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ abstract class AbstractVaultBrowserViewModel(
|
|||||||
list.add(tree)
|
list.add(tree)
|
||||||
while (opened.containsKey(tree.value.uuid)) {
|
while (opened.containsKey(tree.value.uuid)) {
|
||||||
val child = opened.getValue(tree.value.uuid)
|
val child = opened.getValue(tree.value.uuid)
|
||||||
val nextTree = Tree<IStorageInfo>(child)
|
val nextTree = Tree(child)
|
||||||
tree.children = listOf(nextTree)
|
tree.children = listOf(nextTree)
|
||||||
tree = nextTree
|
tree = nextTree
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
@@ -47,19 +50,28 @@ fun VaultBrowserScreen(
|
|||||||
) {
|
) {
|
||||||
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
LaunchedEffect(Unit) {
|
var pendingNotification by remember { mutableStateOf<UserNotification?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
viewModel.userNotifications.collect { notification ->
|
viewModel.userNotifications.collect { notification ->
|
||||||
val text = when (notification) {
|
pendingNotification = notification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val notificationText = when (val notification = pendingNotification) {
|
||||||
is UserNotification.TextRes -> {
|
is UserNotification.TextRes -> {
|
||||||
if (notification.formatArgs.isEmpty()) {
|
if (notification.formatArgs.isEmpty()) {
|
||||||
context.getString(notification.id)
|
stringResource(notification.id)
|
||||||
} else {
|
} else {
|
||||||
context.getString(notification.id, *notification.formatArgs.toTypedArray())
|
stringResource(notification.id, *notification.formatArgs.toTypedArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserNotification.Plain -> notification.message
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.github.nullptroma.wallenc.ui.screens.sync
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -41,7 +40,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -59,19 +57,19 @@ fun StorageSyncScreen(
|
|||||||
) {
|
) {
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val context = LocalContext.current
|
val userMessageText = when (val um = state.userMessage) {
|
||||||
LaunchedEffect(state.userMessage) {
|
|
||||||
val um = state.userMessage ?: return@LaunchedEffect
|
|
||||||
val text = when (um) {
|
|
||||||
is UserNotification.TextRes -> {
|
is UserNotification.TextRes -> {
|
||||||
if (um.formatArgs.isEmpty()) {
|
if (um.formatArgs.isEmpty()) {
|
||||||
context.getString(um.id)
|
stringResource(um.id)
|
||||||
} else {
|
} else {
|
||||||
context.getString(um.id, *um.formatArgs.toTypedArray())
|
stringResource(um.id, *um.formatArgs.toTypedArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is UserNotification.Plain -> um.message
|
is UserNotification.Plain -> um.message
|
||||||
|
null -> null
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(state.userMessage) {
|
||||||
|
val text = userMessageText ?: return@LaunchedEffect
|
||||||
snackbarHostState.showSnackbar(text)
|
snackbarHostState.showSnackbar(text)
|
||||||
viewModel.consumeUserMessage()
|
viewModel.consumeUserMessage()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,9 +149,7 @@ class StorageSyncViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withGroupMutationBusy {
|
withGroupMutationBusy {
|
||||||
val storage = storageByUuid[storageUuid]
|
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 isEncrypted = storage.metaInfo.value.encInfo != null
|
||||||
val result = groupsUseCase.addStorageToGroup(
|
val result = groupsUseCase.addStorageToGroup(
|
||||||
groupId = groupId,
|
groupId = groupId,
|
||||||
@@ -290,8 +288,7 @@ class StorageSyncViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun vaultType(vault: DescribedVault?): String {
|
private fun vaultType(vault: DescribedVault?): String {
|
||||||
val descriptor = vault?.descriptor
|
return when (val descriptor = vault?.descriptor) {
|
||||||
return when (descriptor) {
|
|
||||||
is VaultDescriptor.LocalDevice -> uiStrings(R.string.vault_type_local_device)
|
is VaultDescriptor.LocalDevice -> uiStrings(R.string.vault_type_local_device)
|
||||||
is VaultDescriptor.LinkedRemote -> uiStrings(R.string.vault_type_remote, descriptor.brand.name)
|
is VaultDescriptor.LinkedRemote -> uiStrings(R.string.vault_type_remote, descriptor.brand.name)
|
||||||
null -> uiStrings(R.string.vault_type_unknown)
|
null -> uiStrings(R.string.vault_type_unknown)
|
||||||
@@ -299,8 +296,7 @@ class StorageSyncViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun vaultTitle(vault: DescribedVault?): String {
|
private fun vaultTitle(vault: DescribedVault?): String {
|
||||||
val descriptor = vault?.descriptor
|
return when (val descriptor = vault?.descriptor) {
|
||||||
return when (descriptor) {
|
|
||||||
is VaultDescriptor.LocalDevice -> uiStrings(R.string.vault_title_local)
|
is VaultDescriptor.LocalDevice -> uiStrings(R.string.vault_title_local)
|
||||||
is VaultDescriptor.LinkedRemote -> descriptor.accountDisplayName
|
is VaultDescriptor.LinkedRemote -> descriptor.accountDisplayName
|
||||||
null -> uiStrings(R.string.vault_title_unknown)
|
null -> uiStrings(R.string.vault_title_unknown)
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import kotlinx.serialization.json.JsonPrimitive
|
|||||||
import kotlinx.serialization.json.buildJsonArray
|
import kotlinx.serialization.json.buildJsonArray
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import kotlinx.serialization.json.contentOrNull
|
||||||
import kotlinx.serialization.json.jsonArray
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -33,10 +32,10 @@ class ManageTextSecretsUseCase {
|
|||||||
storage.accessor.filesUpdates
|
storage.accessor.filesUpdates
|
||||||
.filter { page ->
|
.filter { page ->
|
||||||
page.data.any { file ->
|
page.data.any { file ->
|
||||||
domainFilePathEquals(file.metaInfo.path, StorageDomainDataFiles.TEXT_SECRETS_FILE)
|
domainFilePathEquals(file.metaInfo.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { Unit },
|
.map {},
|
||||||
).map {
|
).map {
|
||||||
mutex.withLock { readAll(storage) }
|
mutex.withLock { readAll(storage) }
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
@@ -155,6 +154,6 @@ class ManageTextSecretsUseCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun domainFilePathEquals(left: String, right: String): Boolean =
|
private fun domainFilePathEquals(path: String): Boolean =
|
||||||
left.trimStart('/') == right.trimStart('/')
|
path.trimStart('/') == StorageDomainDataFiles.TEXT_SECRETS_FILE.trimStart('/')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ class ManageTwoFaTokensUseCase {
|
|||||||
storage.accessor.filesUpdates
|
storage.accessor.filesUpdates
|
||||||
.filter { page ->
|
.filter { page ->
|
||||||
page.data.any { file ->
|
page.data.any { file ->
|
||||||
domainFilePathEquals(file.metaInfo.path, StorageDomainDataFiles.TWO_FA_TOKENS_FILE)
|
domainFilePathEquals(file.metaInfo.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map { Unit },
|
.map {},
|
||||||
).map {
|
).map {
|
||||||
mutex.withLock { readAll(storage) }
|
mutex.withLock { readAll(storage) }
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
@@ -153,6 +153,6 @@ class ManageTwoFaTokensUseCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun domainFilePathEquals(left: String, right: String): Boolean =
|
private fun domainFilePathEquals(path: String): Boolean =
|
||||||
left.trimStart('/') == right.trimStart('/')
|
path.trimStart('/') == StorageDomainDataFiles.TWO_FA_TOKENS_FILE.trimStart('/')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ private class FakeStorage : IStorage {
|
|||||||
|
|
||||||
private class FakeMetaInfo : IStorageMetaInfo {
|
private class FakeMetaInfo : IStorageMetaInfo {
|
||||||
override val encInfo: StorageEncryptionInfo? = null
|
override val encInfo: StorageEncryptionInfo? = null
|
||||||
override val name: String? = "Fake"
|
override val name: String = "Fake"
|
||||||
override val lastModified: Instant = Instant.now()
|
override val lastModified: Instant = Instant.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user