Minor clean-up

This commit is contained in:
Artem Chepurnoy 2023-12-30 11:33:41 +02:00
parent 6307893feb
commit 6748218769
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
32 changed files with 52 additions and 127 deletions

View File

@ -7,12 +7,9 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -42,19 +39,14 @@ import com.artemchep.keyguard.feature.keyguard.ManualAppScreenOnCreate
import com.artemchep.keyguard.feature.keyguard.ManualAppScreenOnLoading
import com.artemchep.keyguard.feature.keyguard.ManualAppScreenOnMain
import com.artemchep.keyguard.feature.keyguard.ManualAppScreenOnUnlock
import com.artemchep.keyguard.platform.crashlyticsIsEnabled
import com.artemchep.keyguard.platform.recordException
import com.artemchep.keyguard.platform.recordLog
import com.artemchep.keyguard.provider.bitwarden.CreatePasskey
import com.artemchep.keyguard.common.service.passkey.entity.CreatePasskey
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.OtherScaffold
import com.artemchep.keyguard.ui.theme.Dimens
import com.artemchep.keyguard.ui.theme.combineAlpha
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first

View File

@ -11,7 +11,7 @@ import androidx.credentials.provider.ProviderCreateCredentialRequest
import androidx.credentials.webauthn.Cbor
import com.artemchep.keyguard.common.model.AddPasskeyCipherRequest
import com.artemchep.keyguard.common.service.text.Base64Service
import com.artemchep.keyguard.provider.bitwarden.CreatePasskey
import com.artemchep.keyguard.common.service.passkey.entity.CreatePasskey
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObjectBuilder

View File

@ -1,6 +1,6 @@
package com.artemchep.keyguard.android
import com.artemchep.keyguard.provider.bitwarden.CreatePasskeyPubKeyCredParams
import com.artemchep.keyguard.common.service.passkey.entity.CreatePasskeyPubKeyCredParams
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.PrivateKey

View File

@ -7,7 +7,7 @@ import com.artemchep.keyguard.common.io.bind
import com.artemchep.keyguard.common.service.crypto.CryptoGenerator
import com.artemchep.keyguard.common.service.gpmprivapps.PrivilegedAppsService
import com.artemchep.keyguard.common.usecase.impl.isSubdomain
import com.artemchep.keyguard.provider.bitwarden.CreatePasskeyAttestation
import com.artemchep.keyguard.common.service.passkey.entity.CreatePasskeyAttestation
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get

View File

@ -14,8 +14,8 @@ import com.artemchep.keyguard.common.service.download.DownloadManager
import com.artemchep.keyguard.common.service.download.DownloadProgress
import com.artemchep.keyguard.common.service.text.Base64Service
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.ui.CodeException
import com.artemchep.keyguard.ui.getHttpCode
import com.artemchep.keyguard.common.util.CodeException
import com.artemchep.keyguard.common.util.getHttpCode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job

View File

@ -2,7 +2,7 @@ package com.artemchep.keyguard.android.downloader.journal.room
import androidx.room.Embedded
import androidx.room.Entity
import com.artemchep.keyguard.ui.canRetry
import com.artemchep.keyguard.common.util.canRetry
import kotlinx.datetime.Instant
@Entity(

View File

@ -5,8 +5,6 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.pm.ServiceInfo
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService
import androidx.work.BackoffPolicy
@ -18,13 +16,11 @@ import androidx.work.ForegroundInfo
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.Operation
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.artemchep.keyguard.android.Notifications
import com.artemchep.keyguard.android.downloader.journal.DownloadRepository
import com.artemchep.keyguard.android.downloader.receiver.AttachmentDownloadActionReceiver
import com.artemchep.keyguard.android.downloader.withId
import com.artemchep.keyguard.common.R
import com.artemchep.keyguard.common.io.attempt
import com.artemchep.keyguard.common.io.bind
@ -33,8 +29,8 @@ import com.artemchep.keyguard.common.io.toIO
import com.artemchep.keyguard.common.service.download.DownloadManager
import com.artemchep.keyguard.common.service.download.DownloadProgress
import com.artemchep.keyguard.feature.filepicker.humanReadableByteCountSI
import com.artemchep.keyguard.ui.canRetry
import com.artemchep.keyguard.ui.getHttpCode
import com.artemchep.keyguard.common.util.canRetry
import com.artemchep.keyguard.common.util.getHttpCode
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.onEach

View File

@ -0,0 +1,6 @@
package com.artemchep.keyguard
const val URL_JUST_DELETE_ME = "https://justdeleteme.xyz/"
const val URL_HAVE_I_BEEN_PWNED = "https://haveibeenpwned.com/"
const val URL_2FA = "https://2fa.directory/"
const val URL_PASSKEYS = "https://passkeys.directory/"

View File

@ -1,6 +1,6 @@
package com.artemchep.keyguard.android.downloader.journal.room
import com.artemchep.keyguard.ui.canRetry
import com.artemchep.keyguard.common.util.canRetry
import kotlinx.datetime.Instant
data class DownloadInfoEntity2(

View File

@ -1,4 +1,4 @@
package com.artemchep.keyguard.provider.bitwarden
package com.artemchep.keyguard.common.service.passkey.entity
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package com.artemchep.keyguard.ui
package com.artemchep.keyguard.common.util
import kotlin.streams.asSequence

View File

@ -1,4 +1,4 @@
package com.artemchep.keyguard.ui
package com.artemchep.keyguard.common.util
import com.artemchep.keyguard.common.exception.HttpException
import com.artemchep.keyguard.core.store.bitwarden.BitwardenService

View File

@ -1,6 +1,6 @@
package com.artemchep.keyguard.core.store.bitwarden
import com.artemchep.keyguard.ui.canRetry
import com.artemchep.keyguard.common.util.canRetry
import io.ktor.http.HttpStatusCode
import kotlinx.datetime.Instant

View File

@ -3,8 +3,8 @@ package com.artemchep.keyguard.feature.attachments.model
import com.artemchep.keyguard.common.service.download.DownloadProgress
import com.artemchep.keyguard.feature.attachments.SelectableItemState
import com.artemchep.keyguard.ui.ContextItem
import com.artemchep.keyguard.ui.canRetry
import com.artemchep.keyguard.ui.getHttpCode
import com.artemchep.keyguard.common.util.canRetry
import com.artemchep.keyguard.common.util.getHttpCode
import kotlinx.coroutines.flow.StateFlow
data class AttachmentItem(

View File

@ -42,7 +42,7 @@ import com.artemchep.keyguard.ui.OptionsButton
import com.artemchep.keyguard.ui.ScaffoldLazyColumn
import com.artemchep.keyguard.ui.colorizePassword
import com.artemchep.keyguard.ui.icons.Stub
import com.artemchep.keyguard.ui.items.FlatItemSkeleton
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
import com.artemchep.keyguard.ui.theme.monoFontFamily
import com.artemchep.keyguard.ui.toolbar.LargeToolbar
import dev.icerock.moko.resources.compose.stringResource
@ -113,7 +113,7 @@ private fun GeneratorPaneMaster(
private fun LazyListScope.populateGeneratorPaneMasterSkeleton() {
item("skeleton") {
FlatItemSkeleton()
SkeletonItem()
}
}

View File

@ -88,7 +88,7 @@ import com.artemchep.keyguard.feature.home.settings.component.settingWebsiteIcon
import com.artemchep.keyguard.feature.home.settings.component.settingWriteAccessProvider
import com.artemchep.keyguard.feature.navigation.NavigationIcon
import com.artemchep.keyguard.ui.ScaffoldLazyColumn
import com.artemchep.keyguard.ui.items.FlatItemSkeleton
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
import com.artemchep.keyguard.ui.toolbar.LargeToolbar
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@ -358,7 +358,7 @@ fun SettingPaneContent2(
when (val contentState = state.list) {
is Loadable.Loading -> {
item("skeleton") {
FlatItemSkeleton()
SkeletonItem()
}
}

View File

@ -1,6 +1,5 @@
package com.artemchep.keyguard.feature.home.settings.accounts
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
@ -32,8 +31,8 @@ import com.artemchep.keyguard.ui.FabState
import com.artemchep.keyguard.ui.OptionsButton
import com.artemchep.keyguard.ui.ScaffoldLazyColumn
import com.artemchep.keyguard.ui.icons.SyncIcon
import com.artemchep.keyguard.ui.items.FlatItemSkeleton
import com.artemchep.keyguard.ui.selection.SelectionBar
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
import com.artemchep.keyguard.ui.toolbar.LargeToolbar
import dev.icerock.moko.resources.compose.stringResource
@ -145,7 +144,7 @@ fun AccountListScreenContent(
if (state.items.isEmpty()) {
item("header") {
if (state.isLoading) {
FlatItemSkeleton()
SkeletonItem()
} else {
EmptyView(
icon = {

View File

@ -4,7 +4,6 @@ package com.artemchep.keyguard.feature.home.vault.add
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -82,7 +81,6 @@ import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.model.UsernameVariationIcon
import com.artemchep.keyguard.common.model.fold
import com.artemchep.keyguard.common.model.getOrNull
import com.artemchep.keyguard.common.model.title
import com.artemchep.keyguard.common.model.titleH
import com.artemchep.keyguard.common.service.logging.LogRepository
import com.artemchep.keyguard.feature.auth.common.TextFieldModel2
@ -135,7 +133,7 @@ import com.artemchep.keyguard.ui.icons.KeyguardOrganization
import com.artemchep.keyguard.ui.icons.KeyguardTwoFa
import com.artemchep.keyguard.ui.icons.KeyguardWebsite
import com.artemchep.keyguard.ui.icons.icon
import com.artemchep.keyguard.ui.markdown.Md
import com.artemchep.keyguard.ui.markdown.MarkdownText
import com.artemchep.keyguard.ui.shimmer.shimmer
import com.artemchep.keyguard.ui.skeleton.SkeletonText
import com.artemchep.keyguard.ui.skeleton.SkeletonTextField
@ -882,7 +880,7 @@ private fun NoteTextField(
text = "Markdown",
style = MaterialTheme.typography.titleLarge,
)
Md(
MarkdownText(
modifier = Modifier
.padding(
horizontal = Dimens.horizontalPadding,

View File

@ -17,7 +17,7 @@ import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.ExpandedIfNotEmpty
import com.artemchep.keyguard.ui.FlatItem
import com.artemchep.keyguard.ui.icons.VisibilityIcon
import com.artemchep.keyguard.ui.markdown.Md
import com.artemchep.keyguard.ui.markdown.MarkdownText
import com.artemchep.keyguard.ui.theme.Dimens
import dev.icerock.moko.resources.compose.stringResource
@ -79,7 +79,7 @@ fun VaultViewNoteItem(
) {
SelectionContainer {
if (item.markdown) {
Md(
MarkdownText(
markdown = item.text,
)
} else {

View File

@ -22,6 +22,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import com.artemchep.keyguard.URL_JUST_DELETE_ME
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.model.flatMap
import com.artemchep.keyguard.common.model.getOrNull
@ -39,7 +40,6 @@ import com.artemchep.keyguard.ui.ScaffoldLazyColumn
import com.artemchep.keyguard.ui.focus.FocusRequester2
import com.artemchep.keyguard.ui.focus.focusRequester2
import com.artemchep.keyguard.ui.icons.IconBox
import com.artemchep.keyguard.ui.poweredby.URL_JUST_DELETE_ME
import com.artemchep.keyguard.ui.pulltosearch.PullToSearch
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
import com.artemchep.keyguard.ui.toolbar.CustomToolbar

View File

@ -30,7 +30,7 @@ import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.icons.ChevronIcon
import com.artemchep.keyguard.ui.icons.IconBox
import com.artemchep.keyguard.ui.icons.icon
import com.artemchep.keyguard.ui.markdown.Md
import com.artemchep.keyguard.ui.markdown.MarkdownText
import com.artemchep.keyguard.ui.poweredby.PoweredByJustDeleteMe
import com.artemchep.keyguard.ui.theme.Dimens
import com.artemchep.keyguard.ui.theme.combineAlpha
@ -66,7 +66,7 @@ fun JustDeleteMeScreen(
Column {
val notes = args.justDeleteMe.notes
if (notes != null) {
Md(
MarkdownText(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
markdown = notes,

View File

@ -5,7 +5,7 @@ import arrow.core.partially1
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.feature.navigation.state.navigatePopSelf
import com.artemchep.keyguard.feature.navigation.state.produceScreenState
import com.artemchep.keyguard.ui.asCodePointsSequence
import com.artemchep.keyguard.common.util.asCodePointsSequence
import kotlinx.coroutines.flow.map
@Composable

View File

@ -22,6 +22,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import com.artemchep.keyguard.URL_PASSKEYS
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.model.flatMap
import com.artemchep.keyguard.common.model.getOrNull
@ -38,7 +39,6 @@ import com.artemchep.keyguard.ui.ScaffoldLazyColumn
import com.artemchep.keyguard.ui.focus.FocusRequester2
import com.artemchep.keyguard.ui.focus.focusRequester2
import com.artemchep.keyguard.ui.icons.IconBox
import com.artemchep.keyguard.ui.poweredby.URL_PASSKEYS
import com.artemchep.keyguard.ui.pulltosearch.PullToSearch
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
import com.artemchep.keyguard.ui.toolbar.CustomToolbar

View File

@ -31,7 +31,7 @@ import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.DisabledEmphasisAlpha
import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.icons.icon
import com.artemchep.keyguard.ui.markdown.Md
import com.artemchep.keyguard.ui.markdown.MarkdownText
import com.artemchep.keyguard.ui.poweredby.PoweredByPasskeys
import com.artemchep.keyguard.ui.skeleton.SkeletonText
import com.artemchep.keyguard.ui.theme.Dimens
@ -130,7 +130,7 @@ fun PasskeysViewScreen(
val state = loadableState.getOrNull()?.content?.getOrNull()
val notes = state?.model?.notes
if (notes != null) {
Md(
MarkdownText(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
markdown = notes,

View File

@ -22,6 +22,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import com.artemchep.keyguard.URL_2FA
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.model.flatMap
import com.artemchep.keyguard.common.model.getOrNull
@ -38,7 +39,6 @@ import com.artemchep.keyguard.ui.ScaffoldLazyColumn
import com.artemchep.keyguard.ui.focus.FocusRequester2
import com.artemchep.keyguard.ui.focus.focusRequester2
import com.artemchep.keyguard.ui.icons.IconBox
import com.artemchep.keyguard.ui.poweredby.URL_2FA
import com.artemchep.keyguard.ui.pulltosearch.PullToSearch
import com.artemchep.keyguard.ui.skeleton.SkeletonItem
import com.artemchep.keyguard.ui.toolbar.CustomToolbar

View File

@ -37,7 +37,7 @@ import com.artemchep.keyguard.ui.MediumEmphasisAlpha
import com.artemchep.keyguard.ui.icons.ChevronIcon
import com.artemchep.keyguard.ui.icons.KeyguardTwoFa
import com.artemchep.keyguard.ui.icons.icon
import com.artemchep.keyguard.ui.markdown.Md
import com.artemchep.keyguard.ui.markdown.MarkdownText
import com.artemchep.keyguard.ui.poweredby.PoweredBy2factorauth
import com.artemchep.keyguard.ui.skeleton.SkeletonText
import com.artemchep.keyguard.ui.theme.Dimens
@ -137,7 +137,7 @@ fun TwoFaServiceViewScreen(
val state = loadableState.getOrNull()?.content?.getOrNull()
val notes = state?.model?.notes
if (notes != null) {
Md(
MarkdownText(
modifier = Modifier
.padding(horizontal = Dimens.horizontalPadding),
markdown = notes,

View File

@ -6,8 +6,8 @@ import com.artemchep.keyguard.core.store.DatabaseManager
import com.artemchep.keyguard.core.store.bitwarden.BitwardenService
import com.artemchep.keyguard.core.store.bitwarden.BitwardenToken
import com.artemchep.keyguard.provider.bitwarden.api.refresh
import com.artemchep.keyguard.ui.canRetry
import com.artemchep.keyguard.ui.getHttpCode
import com.artemchep.keyguard.common.util.canRetry
import com.artemchep.keyguard.common.util.getHttpCode
import io.ktor.client.HttpClient
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.delay

View File

@ -1,64 +0,0 @@
package com.artemchep.keyguard.ui.items
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.artemchep.keyguard.ui.Avatar
import com.artemchep.keyguard.ui.DisabledEmphasisAlpha
import com.artemchep.keyguard.ui.FlatItemLayout
import com.artemchep.keyguard.ui.shimmer.shimmer
@Composable
fun FlatItemSkeleton(
avatar: Boolean = false,
fill: Boolean = false,
titleWidthFraction: Float = 0.45f,
textWidthFraction: Float = 0.33f,
) {
val contentColor = LocalContentColor.current.copy(alpha = DisabledEmphasisAlpha)
FlatItemLayout(
modifier = Modifier
.shimmer(),
backgroundColor = contentColor
.takeIf { fill }
?.copy(
alpha = 0.08f,
)
?: Color.Unspecified,
leading = if (avatar) {
// composable
{
Avatar {
}
}
} else {
null
},
content = {
Box(
Modifier
.height(18.dp)
.fillMaxWidth(titleWidthFraction)
.clip(MaterialTheme.shapes.medium)
.background(contentColor),
)
Box(
Modifier
.padding(top = 4.dp)
.height(14.dp)
.fillMaxWidth(textWidthFraction)
.clip(MaterialTheme.shapes.medium)
.background(contentColor),
)
},
)
}

View File

@ -10,7 +10,7 @@ import com.halilibo.richtext.ui.resolveDefaults
import com.halilibo.richtext.ui.string.RichTextStringStyle
@Composable
fun Md(
fun MarkdownText(
modifier: Modifier = Modifier,
markdown: String,
) {

View File

@ -14,6 +14,10 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.artemchep.keyguard.URL_2FA
import com.artemchep.keyguard.URL_HAVE_I_BEEN_PWNED
import com.artemchep.keyguard.URL_JUST_DELETE_ME
import com.artemchep.keyguard.URL_PASSKEYS
import com.artemchep.keyguard.feature.navigation.LocalNavigationController
import com.artemchep.keyguard.feature.navigation.NavigationIntent
import com.artemchep.keyguard.res.Res
@ -21,11 +25,6 @@ import com.artemchep.keyguard.ui.DisabledEmphasisAlpha
import com.artemchep.keyguard.ui.theme.combineAlpha
import dev.icerock.moko.resources.compose.stringResource
const val URL_JUST_DELETE_ME = "https://justdeleteme.xyz/"
const val URL_HAVE_I_BEEN_PWNED = "https://haveibeenpwned.com/"
const val URL_2FA = "https://2fa.directory/"
const val URL_PASSKEYS = "https://passkeys.directory/"
@Composable
fun PoweredByJustDeleteMe(
modifier: Modifier = Modifier,

View File

@ -3,7 +3,7 @@ package com.artemchep.keyguard.ui.totp
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import com.artemchep.keyguard.common.model.TotpCode
import com.artemchep.keyguard.ui.asCodePointsSequence
import com.artemchep.keyguard.common.util.asCodePointsSequence
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList

View File

@ -10,14 +10,13 @@ import com.artemchep.keyguard.common.service.download.DownloadManager
import com.artemchep.keyguard.common.service.download.DownloadProgress
import com.artemchep.keyguard.common.service.text.Base64Service
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.ui.CodeException
import com.artemchep.keyguard.ui.getHttpCode
import com.artemchep.keyguard.common.util.CodeException
import com.artemchep.keyguard.common.util.getHttpCode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.last