feat: Default match detection #708

This commit is contained in:
Artem Chepurnyi 2024-11-24 00:01:25 +02:00
parent 1927b066d8
commit bdd23cb988
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
29 changed files with 363 additions and 93 deletions

View File

@ -17,10 +17,12 @@ import com.artemchep.keyguard.common.model.LinkInfoPlatform
import com.artemchep.keyguard.common.model.contains
import com.artemchep.keyguard.common.service.extract.LinkInfoExtractor
import com.artemchep.keyguard.common.usecase.CipherUrlCheck
import com.artemchep.keyguard.common.usecase.GetAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.GetSuggestions
import com.artemchep.keyguard.feature.home.vault.search.findAlike
import io.ktor.http.Url
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import org.kodein.di.DirectDI
import org.kodein.di.allInstances
import org.kodein.di.instance
@ -113,10 +115,12 @@ private const val scoreThreshold = 0.1f
class GetSuggestionsImpl(
private val androidExtractors: List<LinkInfoExtractor<LinkInfoPlatform.Android, LinkInfoAndroid>>,
private val getAutofillDefaultMatchDetection: GetAutofillDefaultMatchDetection,
private val cipherUrlCheck: CipherUrlCheck,
) : GetSuggestions<Any?> {
constructor(directDI: DirectDI) : this(
androidExtractors = directDI.allInstances(),
getAutofillDefaultMatchDetection = directDI.instance(),
cipherUrlCheck = directDI.instance(),
)
@ -179,6 +183,8 @@ class GetSuggestionsImpl(
)
}
val defaultMatchDetection = getAutofillDefaultMatchDetection()
.first()
val c = ciphers
.parallelSearch { wrapper ->
val cipher = getter.get(wrapper)
@ -190,8 +196,13 @@ class GetSuggestionsImpl(
AutofillHint.PASSWORD in target.hints
) {
run {
val scoreByWebDomainRaw =
autofillTargetWeb?.findByWebDomain(cipher, tokens, cipherUrlCheck)
val scoreByWebDomainRaw = autofillTargetWeb
?.findByWebDomain(
secret = cipher,
tokens = tokens,
cipherUrlCheck = cipherUrlCheck,
defaultMatchDetection = defaultMatchDetection,
)
val scoreByWebDomain = scoreByWebDomainRaw
?: 0f
if (scoreByWebDomain > scoreThreshold) {
@ -358,6 +369,7 @@ private suspend fun GetSuggestionsImpl.AutofillTargetWeb.findByWebDomain(
secret: DSecret,
tokens: Set<String>,
cipherUrlCheck: CipherUrlCheck,
defaultMatchDetection: DSecret.Uri.MatchType,
): Float = run {
val webHost = webUrl.host
val webUrl = webUrl.toString()
@ -365,13 +377,13 @@ private suspend fun GetSuggestionsImpl.AutofillTargetWeb.findByWebDomain(
// the cipher, then we give it maximum priority.
val scoreByUri = secret.uris
.map { uri ->
val match = cipherUrlCheck.invoke(uri, webUrl)
val match = cipherUrlCheck.invoke(uri, webUrl, defaultMatchDetection)
.attempt()
.bind()
.getOrElse { false }
if (match) {
@Suppress("MoveVariableDeclarationIntoWhen")
val matchType = uri.match ?: DSecret.Uri.MatchType.default
val matchType = uri.match ?: defaultMatchDetection
when (matchType) {
DSecret.Uri.MatchType.Domain -> 10f
DSecret.Uri.MatchType.Host -> 20f

View File

@ -351,6 +351,8 @@
<string name="uri_unsecure">Unsecure</string>
<string name="uri_match_app_title">Match app</string>
<string name="uri_match_detection_title">Match detection</string>
<string name="uri_match_detection_default_title">Default</string>
<string name="uri_match_detection_default_note">Uses a global Default match detection behavior selected in Settings → Autofill → Default URI match detection. Base domain matching is the default option.</string>
<string name="uri_match_detection_domain_title">Base domain</string>
<string name="uri_match_detection_domain_note">Match resources by top-level and second-level domain.</string>
<string name="uri_match_detection_host_title">Host</string>
@ -1117,6 +1119,7 @@
3. Confirm and restart Chrome</string>
<string name="pref_item_autofill_auto_copy_otp_title">Auto-copy one-time passwords</string>
<string name="pref_item_autofill_auto_copy_otp_text">When filling a login information, automatically copy one-time passwords</string>
<string name="pref_item_autofill_default_match_detection_title">Default match detection</string>
<string name="pref_item_autofill_inline_suggestions_title">Inline suggestions</string>
<string name="pref_item_autofill_inline_suggestions_text">Embeds autofill suggestions directly into compatible keyboards</string>
<string name="pref_item_autofill_manual_selection_title">Manual selection</string>

View File

@ -21,6 +21,7 @@ import com.artemchep.keyguard.common.usecase.CipherExpiringCheck
import com.artemchep.keyguard.common.usecase.CipherIncompleteCheck
import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlCheck
import com.artemchep.keyguard.common.usecase.CipherUrlDuplicateCheck
import com.artemchep.keyguard.common.usecase.GetAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.GetBreaches
import com.artemchep.keyguard.common.usecase.GetPasskeys
import com.artemchep.keyguard.common.usecase.GetTwoFa
@ -47,6 +48,7 @@ import com.artemchep.keyguard.ui.icons.KeyguardReusedPassword
import com.artemchep.keyguard.ui.icons.KeyguardTwoFa
import com.artemchep.keyguard.ui.icons.KeyguardUnsecureWebsites
import io.ktor.http.Url
import kotlinx.coroutines.flow.first
import kotlinx.datetime.Clock
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -769,6 +771,8 @@ sealed interface DFilter {
directDI: DirectDI,
ciphers: List<DSecret>,
): Set<String> = ioEffect {
val getAutofillDefaultMatchDetection =
directDI.instance<GetAutofillDefaultMatchDetection>()
val check: CipherBreachCheck = directDI.instance()
val getBreaches: GetBreaches = directDI.instance()
@ -778,6 +782,8 @@ sealed interface DFilter {
}
.bind()
val defaultMatchDetection = getAutofillDefaultMatchDetection()
.first()
ciphers
.filter { cipher ->
val shouldIgnore = shouldIgnore(cipher)
@ -785,7 +791,7 @@ sealed interface DFilter {
return@filter false
}
check(cipher, breaches)
check(cipher, breaches, defaultMatchDetection)
.handleError { false }
.bind()
}
@ -1244,7 +1250,12 @@ sealed interface DFilter {
directDI: DirectDI,
ciphers: List<DSecret>,
) = kotlin.run {
val getAutofillDefaultMatchDetection =
directDI.instance<GetAutofillDefaultMatchDetection>()
val cipherUrlDuplicateCheck = directDI.instance<CipherUrlDuplicateCheck>()
val defaultMatchDetection = getAutofillDefaultMatchDetection()
.first()
ciphers
.filter { cipher ->
if (shouldIgnore(cipher)) {
@ -1263,7 +1274,7 @@ sealed interface DFilter {
val a = uris[i]
val b = uris[j]
val duplicate = cipherUrlDuplicateCheck(a, b)
val duplicate = cipherUrlDuplicateCheck(a, b, defaultMatchDetection)
.attempt()
.bind()
.isRight { it != null }

View File

@ -429,7 +429,8 @@ fun DSecret.Type.titleH() = when (this) {
DSecret.Type.None -> Res.string.cipher_type_unknown
}
fun DSecret.Uri.MatchType.titleH() = when (this) {
fun DSecret.Uri.MatchType?.titleH() = when (this) {
null -> Res.string.uri_match_detection_default_title
DSecret.Uri.MatchType.Domain -> Res.string.uri_match_detection_domain_title
DSecret.Uri.MatchType.Host -> Res.string.uri_match_detection_host_title
DSecret.Uri.MatchType.StartsWith -> Res.string.uri_match_detection_startswith_title

View File

@ -0,0 +1,27 @@
package com.artemchep.keyguard.common.model;
enum class MatchDetection(
val matchType: DSecret.Uri.MatchType?,
) {
Default(null),
Domain(DSecret.Uri.MatchType.Domain),
Host(DSecret.Uri.MatchType.Host),
StartsWith(DSecret.Uri.MatchType.StartsWith),
Exact(DSecret.Uri.MatchType.Exact),
RegularExpression(DSecret.Uri.MatchType.RegularExpression),
Never(DSecret.Uri.MatchType.Never);
companion object {
fun valueOf(
matchType: DSecret.Uri.MatchType?,
): MatchDetection = when (matchType) {
null -> Default
DSecret.Uri.MatchType.Domain -> Domain
DSecret.Uri.MatchType.Host -> Host
DSecret.Uri.MatchType.StartsWith -> StartsWith
DSecret.Uri.MatchType.Exact -> Exact
DSecret.Uri.MatchType.RegularExpression -> RegularExpression
DSecret.Uri.MatchType.Never -> Never
}
}
}

View File

@ -13,6 +13,8 @@ import kotlin.time.Duration
* @author Artem Chepurnyi
*/
interface SettingsReadRepository {
fun getAutofillDefaultMatchDetection(): Flow<String>
fun getAutofillInlineSuggestions(): Flow<Boolean>
fun getAutofillManualSelection(): Flow<Boolean>

View File

@ -13,6 +13,10 @@ import kotlin.time.Duration
* @author Artem Chepurnyi
*/
interface SettingsReadWriteRepository : SettingsReadRepository {
fun setAutofillDefaultMatchDetection(
matchDetection: String,
): IO<Unit>
fun setAutofillInlineSuggestions(
inlineSuggestions: Boolean,
): IO<Unit>

View File

@ -36,6 +36,7 @@ class SettingsRepositoryImpl(
private val json: Json,
) : SettingsReadWriteRepository {
companion object {
private const val KEY_AUTOFILL_DEFAULT_MATCH_DETECTION = "autofill.default_match_detection"
private const val KEY_AUTOFILL_INLINE_SUGGESTIONS = "autofill.inline_suggestions"
private const val KEY_AUTOFILL_MANUAL_SELECTION = "autofill.manual_selection"
private const val KEY_AUTOFILL_RESPECT_AUTOFILL_OFF = "autofill.respect_autofill_off"
@ -82,6 +83,9 @@ class SettingsRepositoryImpl(
private const val NONE_DURATION = -1L
}
private val autofillDefaultMatchDetectionPref =
store.getString(KEY_AUTOFILL_DEFAULT_MATCH_DETECTION, "")
private val autofillInlineSuggestionsPref =
store.getBoolean(KEY_AUTOFILL_INLINE_SUGGESTIONS, true)
@ -223,6 +227,13 @@ class SettingsRepositoryImpl(
json = directDI.instance(),
)
override fun setAutofillDefaultMatchDetection(
matchDetection: String,
) = autofillDefaultMatchDetectionPref
.setAndCommit(matchDetection)
override fun getAutofillDefaultMatchDetection() = autofillDefaultMatchDetectionPref
override fun setAutofillInlineSuggestions(inlineSuggestions: Boolean) =
autofillInlineSuggestionsPref
.setAndCommit(inlineSuggestions)

View File

@ -4,4 +4,8 @@ import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.model.DSecret
import com.artemchep.keyguard.provider.bitwarden.entity.HibpBreachGroup
interface CipherBreachCheck : (DSecret, HibpBreachGroup) -> IO<Boolean>
interface CipherBreachCheck : (
DSecret,
HibpBreachGroup,
DSecret.Uri.MatchType,
) -> IO<Boolean>

View File

@ -3,4 +3,8 @@ package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.model.DSecret
interface CipherUrlCheck : (DSecret.Uri, String) -> IO<Boolean>
interface CipherUrlCheck : (
DSecret.Uri,
String,
DSecret.Uri.MatchType,
) -> IO<Boolean>

View File

@ -3,4 +3,8 @@ package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.model.DSecret
interface CipherUrlDuplicateCheck : (DSecret.Uri, DSecret.Uri) -> IO<DSecret.Uri?>
interface CipherUrlDuplicateCheck : (
DSecret.Uri,
DSecret.Uri,
DSecret.Uri.MatchType,
) -> IO<DSecret.Uri?>

View File

@ -0,0 +1,6 @@
package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.model.DSecret
import kotlinx.coroutines.flow.Flow
interface GetAutofillDefaultMatchDetection : () -> Flow<DSecret.Uri.MatchType>

View File

@ -0,0 +1,6 @@
package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.model.DSecret
interface PutAutofillDefaultMatchDetection : (DSecret.Uri.MatchType) -> IO<Unit>

View File

@ -0,0 +1,37 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.model.DSecret
import com.artemchep.keyguard.common.service.settings.SettingsReadRepository
import com.artemchep.keyguard.common.usecase.GetAutofillDefaultMatchDetection
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import org.kodein.di.DirectDI
import org.kodein.di.instance
class GetAutofillDefaultMatchDetectionImpl(
settingsReadRepository: SettingsReadRepository,
) : GetAutofillDefaultMatchDetection {
private val sharedFlow = settingsReadRepository.getAutofillDefaultMatchDetection()
.map { key -> findOrDefault(key) }
.distinctUntilChanged()
.shareIn(
scope = GlobalScope,
started = SharingStarted.WhileSubscribed(5000L),
replay = 1,
)
constructor(directDI: DirectDI) : this(
settingsReadRepository = directDI.instance(),
)
override fun invoke() = sharedFlow
private fun findOrDefault(key: String) = findOrNull(key)
?: DSecret.Uri.MatchType.default
private fun findOrNull(key: String) = DSecret.Uri.MatchType.entries
.firstOrNull { it.name.equals(key, ignoreCase = true) }
}

View File

@ -0,0 +1,21 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.model.DSecret
import com.artemchep.keyguard.common.service.settings.SettingsReadWriteRepository
import com.artemchep.keyguard.common.usecase.PutAutofillDefaultMatchDetection
import org.kodein.di.DirectDI
import org.kodein.di.instance
class PutAutofillDefaultMatchDetectionImpl(
private val settingsReadWriteRepository: SettingsReadWriteRepository,
) : PutAutofillDefaultMatchDetection {
constructor(directDI: DirectDI) : this(
settingsReadWriteRepository = directDI.instance(),
)
override fun invoke(
matchDetection: DSecret.Uri.MatchType,
): IO<Unit> = settingsReadWriteRepository
.setAutofillDefaultMatchDetection(matchDetection.name)
}

View File

@ -25,6 +25,7 @@ import com.artemchep.keyguard.common.usecase.CipherExpiringCheck
import com.artemchep.keyguard.common.usecase.CipherIncompleteCheck
import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlCheck
import com.artemchep.keyguard.common.usecase.CipherUrlDuplicateCheck
import com.artemchep.keyguard.common.usecase.GetAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.GetBreaches
import com.artemchep.keyguard.common.usecase.GetCheckPasskeys
import com.artemchep.keyguard.common.usecase.GetCheckPwnedPasswords
@ -53,6 +54,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@ -318,6 +320,7 @@ class WatchtowerPasswordPwned(
}
class WatchtowerWebsitePwned(
private val getAutofillDefaultMatchDetection: GetAutofillDefaultMatchDetection,
private val cipherBreachCheck: CipherBreachCheck,
private val getBreaches: GetBreaches,
private val getCheckPwnedServices: GetCheckPwnedServices,
@ -326,6 +329,7 @@ class WatchtowerWebsitePwned(
get() = DWatchtowerAlertType.PWNED_WEBSITE.value
constructor(directDI: DirectDI) : this(
getAutofillDefaultMatchDetection = directDI.instance(),
cipherBreachCheck = directDI.instance(),
getBreaches = directDI.instance(),
getCheckPwnedServices = directDI.instance(),
@ -333,6 +337,8 @@ class WatchtowerWebsitePwned(
override fun version() = combineJoinToVersion(
getDatabaseVersionFlow(),
getAutofillDefaultMatchDetection()
.map { it.name },
getCheckPwnedServices()
.map {
it.int.toString()
@ -349,8 +355,11 @@ class WatchtowerWebsitePwned(
override suspend fun process(
ciphers: List<DSecret>,
): List<WatchtowerClientResult> {
val defaultMatchDetection = getAutofillDefaultMatchDetection()
.first()
val set = buildDuplicatesState(
ciphers = ciphers,
defaultMatchDetection = defaultMatchDetection,
)
return ciphers
.map { cipher ->
@ -375,6 +384,7 @@ class WatchtowerWebsitePwned(
private suspend fun buildDuplicatesState(
ciphers: List<DSecret>,
defaultMatchDetection: DSecret.Uri.MatchType,
): Set<String> = ioEffect {
val breaches = getBreaches()
.handleError {
@ -389,7 +399,7 @@ class WatchtowerWebsitePwned(
return@filter false
}
cipherBreachCheck(cipher, breaches)
cipherBreachCheck(cipher, breaches, defaultMatchDetection)
.handleError { false }
.bind()
}
@ -766,24 +776,32 @@ class WatchtowerInactiveTfa(
}
class WatchtowerDuplicateUris(
private val getAutofillDefaultMatchDetection: GetAutofillDefaultMatchDetection,
private val cipherUrlDuplicateCheck: CipherUrlDuplicateCheck,
) : WatchtowerClientTyped {
override val type: Long
get() = DWatchtowerAlertType.DUPLICATE_URIS.value
constructor(directDI: DirectDI) : this(
getAutofillDefaultMatchDetection = directDI.instance(),
cipherUrlDuplicateCheck = directDI.instance(),
)
override fun version() = flowOf("1")
override fun version() = combineJoinToVersion(
getAutofillDefaultMatchDetection()
.map { it.name },
)
override suspend fun process(
ciphers: List<DSecret>,
): List<WatchtowerClientResult> {
val defaultMatchDetection = getAutofillDefaultMatchDetection()
.first()
return ciphers
.map { cipher ->
val value = hasAlert(
cipher = cipher,
defaultMatchDetection = defaultMatchDetection,
)
val threat = value != null
WatchtowerClientResult(
@ -796,17 +814,19 @@ class WatchtowerDuplicateUris(
private suspend fun hasAlert(
cipher: DSecret,
defaultMatchDetection: DSecret.Uri.MatchType,
): String? {
val shouldIgnore = shouldIgnore(cipher)
if (shouldIgnore) {
return null
}
return match(cipher)
return match(cipher, defaultMatchDetection)
}
private suspend fun match(
cipher: DSecret,
defaultMatchDetection: DSecret.Uri.MatchType,
) = kotlin.run {
val uris = cipher.uris
if (uris.isEmpty()) {
@ -822,7 +842,7 @@ class WatchtowerDuplicateUris(
val a = uris[i]
val b = uris[j]
val duplicate = cipherUrlDuplicateCheck(a, b)
val duplicate = cipherUrlDuplicateCheck(a, b, defaultMatchDetection)
.attempt()
.bind()
.isRight { it != null }

View File

@ -173,7 +173,7 @@ private val items = listOfNotNull<SettingsItem2>(
text = TextHolder.Res(Res.string.pref_item_autofill_text),
icon = Icons.Outlined.AutoAwesome,
route = AutofillSettingsRoute,
).takeIf { CurrentPlatform.hasAutofill() },
).takeIf { CurrentPlatform.hasAutofill() || !isRelease },
SettingsItem(
id = "security",
title = TextHolder.Res(Res.string.pref_item_security_title),

View File

@ -27,6 +27,7 @@ import com.artemchep.keyguard.feature.home.settings.component.settingAboutTelegr
import com.artemchep.keyguard.feature.home.settings.component.settingApkProvider
import com.artemchep.keyguard.feature.home.settings.component.settingAppIconsProvider
import com.artemchep.keyguard.feature.home.settings.component.settingAutofillCopyTotpProvider
import com.artemchep.keyguard.feature.home.settings.component.settingAutofillDefaultMatchDetectionProvider
import com.artemchep.keyguard.feature.home.settings.component.settingAutofillInlineSuggestionsProvider
import com.artemchep.keyguard.feature.home.settings.component.settingAutofillManualSelectionProvider
import com.artemchep.keyguard.feature.home.settings.component.settingAutofillProvider
@ -117,6 +118,7 @@ object Setting {
const val CREDENTIAL_PROVIDER = "credential_provider"
const val AUTOFILL = "autofill"
const val AUTOFILL_DEFAULT_MATCH_DETECTION = "autofill_default_match_detection"
const val AUTOFILL_INLINE_SUGGESTIONS = "autofill_inline_suggestions"
const val AUTOFILL_MANUAL_SELECTION = "autofill_manual_selection"
const val AUTOFILL_RESPECT_AUTOFILL_OFF = "autofill_respect_autofill_off"
@ -204,6 +206,7 @@ val LocalSettingItemArgs = staticCompositionLocalOf<Any?> {
val hub = mapOf<String, (DirectDI) -> SettingComponent>(
Setting.CREDENTIAL_PROVIDER to ::settingCredentialProviderProvider,
Setting.AUTOFILL to ::settingAutofillProvider,
Setting.AUTOFILL_DEFAULT_MATCH_DETECTION to ::settingAutofillDefaultMatchDetectionProvider,
Setting.AUTOFILL_INLINE_SUGGESTIONS to ::settingAutofillInlineSuggestionsProvider,
Setting.AUTOFILL_MANUAL_SELECTION to ::settingAutofillManualSelectionProvider,
Setting.AUTOFILL_RESPECT_AUTOFILL_OFF to ::settingAutofillRespectAutofillOffProvider,

View File

@ -29,6 +29,7 @@ fun AutofillSettingsScreen() {
SettingPaneItem.Group(
key = "general",
list = persistentListOf(
SettingPaneItem.Item(Setting.AUTOFILL_DEFAULT_MATCH_DETECTION),
SettingPaneItem.Item(Setting.AUTOFILL_INLINE_SUGGESTIONS),
SettingPaneItem.Item(Setting.AUTOFILL_MANUAL_SELECTION),
SettingPaneItem.Item(Setting.AUTOFILL_RESPECT_AUTOFILL_OFF),

View File

@ -0,0 +1,98 @@
package com.artemchep.keyguard.feature.home.settings.component
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Domain
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.artemchep.keyguard.common.io.launchIn
import com.artemchep.keyguard.common.model.DSecret
import com.artemchep.keyguard.common.model.titleH
import com.artemchep.keyguard.common.usecase.GetAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.PutAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.feature.localization.TextHolder
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.res.*
import com.artemchep.keyguard.ui.FlatDropdown
import com.artemchep.keyguard.ui.FlatItemAction
import com.artemchep.keyguard.ui.FlatItemTextContent
import com.artemchep.keyguard.ui.icons.icon
import org.jetbrains.compose.resources.stringResource
import kotlinx.coroutines.flow.map
import org.jetbrains.compose.resources.StringResource
import org.kodein.di.DirectDI
import org.kodein.di.instance
import kotlin.collections.map
fun settingAutofillDefaultMatchDetectionProvider(
directDI: DirectDI,
) = settingAutofillDefaultMatchDetectionProvider(
getAutofillDefaultMatchDetection = directDI.instance(),
putAutofillDefaultMatchDetection = directDI.instance(),
windowCoroutineScope = directDI.instance(),
)
fun settingAutofillDefaultMatchDetectionProvider(
getAutofillDefaultMatchDetection: GetAutofillDefaultMatchDetection,
putAutofillDefaultMatchDetection: PutAutofillDefaultMatchDetection,
windowCoroutineScope: WindowCoroutineScope,
): SettingComponent = getAutofillDefaultMatchDetection().map { matchDetection ->
val text = matchDetection.titleH()
val variants = DSecret.Uri.MatchType.entries
val dropdown = variants
.map { entry ->
val actionSelected = entry == matchDetection
val actionTitleRes = entry.titleH()
FlatItemAction(
title = TextHolder.Res(actionTitleRes),
selected = actionSelected,
onClick = {
putAutofillDefaultMatchDetection(entry)
.launchIn(windowCoroutineScope)
},
)
}
SettingIi(
search = SettingIi.Search(
group = "autofill",
tokens = listOf(
"autofill",
"match",
"detection",
"uri",
),
),
) {
SettingAutofillDefaultMatchDetection(
text = text,
dropdown = dropdown,
)
}
}
@Composable
private fun SettingAutofillDefaultMatchDetection(
text: StringResource,
dropdown: List<FlatItemAction>,
) {
FlatDropdown(
leading = icon<RowScope>(Icons.Outlined.Domain),
content = {
FlatItemTextContent(
title = {
Text(
text = stringResource(Res.string.pref_item_autofill_default_match_detection_title),
)
},
text = {
Text(
text = stringResource(text),
)
},
)
},
dropdown = dropdown,
)
}

View File

@ -36,6 +36,7 @@ import com.artemchep.keyguard.common.model.DSecret
import com.artemchep.keyguard.common.model.KeyPair
import com.artemchep.keyguard.common.model.KeyPairDecor
import com.artemchep.keyguard.common.model.Loadable
import com.artemchep.keyguard.common.model.MatchDetection
import com.artemchep.keyguard.common.model.ToastMessage
import com.artemchep.keyguard.common.model.TotpToken
import com.artemchep.keyguard.common.model.UsernameVariation2
@ -81,6 +82,7 @@ import com.artemchep.keyguard.common.usecase.AddCipher
import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlCheck
import com.artemchep.keyguard.common.usecase.CopyText
import com.artemchep.keyguard.common.usecase.GetAccounts
import com.artemchep.keyguard.common.usecase.GetAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.GetCiphers
import com.artemchep.keyguard.common.usecase.GetCollections
import com.artemchep.keyguard.common.usecase.GetFolders
@ -198,6 +200,7 @@ fun produceAddScreenState(
logRepository = instance(),
clipboardService = instance(),
otpMigrationService = instance(),
getAutofillDefaultMatchDetection = instance(),
cipherUnsecureUrlCheck = instance(),
showMessage = instance(),
addCipher = instance(),
@ -228,6 +231,7 @@ fun produceAddScreenState(
logRepository: LogRepository,
clipboardService: ClipboardService,
otpMigrationService: OtpMigrationService,
getAutofillDefaultMatchDetection: GetAutofillDefaultMatchDetection,
cipherUnsecureUrlCheck: CipherUnsecureUrlCheck,
showMessage: ShowMessage,
addCipher: AddCipher,
@ -363,6 +367,7 @@ fun produceAddScreenState(
val urisFactories = kotlin.run {
val uriFactory = AddStateItemUriFactory(
getAutofillDefaultMatchDetection = getAutofillDefaultMatchDetection,
cipherUnsecureUrlCheck = cipherUnsecureUrlCheck,
)
listOf(
@ -948,6 +953,7 @@ class AddStateItemAttachmentFactory : Foo2Factory<AddStateItem.Attachment<*>, DS
}
class AddStateItemUriFactory(
private val getAutofillDefaultMatchDetection: GetAutofillDefaultMatchDetection,
private val cipherUnsecureUrlCheck: CipherUnsecureUrlCheck,
) : Foo2Factory<AddStateItem.Url<*>, DSecret.Uri> {
override val type: String = "uri"
@ -967,8 +973,8 @@ class AddStateItemUriFactory(
}
val uriMutableState = asComposeState<String>(uriKey)
val matchTypeSink = mutablePersistedFlow("$key.match_type") {
initial?.match ?: DSecret.Uri.MatchType.default
val matchDetectionSink = mutablePersistedFlow("$key.match_detection") {
MatchDetection.valueOf(initial?.match)
}
val actionsAppPickerItem = FlatItemAction(
@ -981,7 +987,7 @@ class AddStateItemUriFactory(
val route = registerRouteResultReceiver(AppPickerRoute) { result ->
if (result is AppPickerResult.Confirm) {
uriMutableState.value = result.uri
matchTypeSink.value = DSecret.Uri.MatchType.default
matchDetectionSink.value = MatchDetection.Default
}
}
val intent = NavigationIntent.NavigateToRoute(
@ -992,16 +998,16 @@ class AddStateItemUriFactory(
)
// Add a ability to change the
// match type via an option.
val actionsMatchTypeItemFlow = matchTypeSink
val actionsMatchTypeItemFlow = matchDetectionSink
.map { selectedMatchType ->
FlatItemAction(
leading = icon(Icons.Stub),
title = Res.string.uri_match_detection_title.wrap(),
text = selectedMatchType.titleH().wrap(),
text = selectedMatchType.matchType.titleH().wrap(),
onClick = onClick {
val items = DSecret.Uri.MatchType.entries
val items = MatchDetection.entries
.map { type ->
val typeTitle = translate(type.titleH())
val typeTitle = translate(type.matchType.titleH())
ConfirmationRoute.Args.Item.EnumItem.Item(
key = type.name,
title = typeTitle,
@ -1013,27 +1019,31 @@ class AddStateItemUriFactory(
value = selectedMatchType.name,
items = items,
docs = mapOf(
DSecret.Uri.MatchType.Domain.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
MatchDetection.Default.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
text = translate(Res.string.uri_match_detection_default_note),
url = "https://bitwarden.com/help/uri-match-detection/#default-match-detection",
),
MatchDetection.Domain.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
text = translate(Res.string.uri_match_detection_domain_note),
url = "https://bitwarden.com/help/uri-match-detection/#base-domain",
),
DSecret.Uri.MatchType.Host.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
MatchDetection.Host.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
text = translate(Res.string.uri_match_detection_host_note),
url = "https://bitwarden.com/help/uri-match-detection/#host",
),
DSecret.Uri.MatchType.StartsWith.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
MatchDetection.StartsWith.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
text = translate(Res.string.uri_match_detection_startswith_note),
url = "https://bitwarden.com/help/uri-match-detection/#starts-with",
),
DSecret.Uri.MatchType.Exact.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
MatchDetection.Exact.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
text = translate(Res.string.uri_match_detection_exact_note),
url = "https://bitwarden.com/help/uri-match-detection/#regular-expression",
),
DSecret.Uri.MatchType.RegularExpression.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
MatchDetection.RegularExpression.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
text = translate(Res.string.uri_match_detection_regex_note),
url = "https://bitwarden.com/help/uri-match-detection/#regular-expression",
),
DSecret.Uri.MatchType.Never.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
MatchDetection.Never.name to ConfirmationRoute.Args.Item.EnumItem.Doc(
text = translate(Res.string.uri_match_detection_never_note),
url = "https://bitwarden.com/help/uri-match-detection/#exact",
),
@ -1041,8 +1051,7 @@ class AddStateItemUriFactory(
),
title = translate(Res.string.uri_match_detection_title),
) { newMatchTypeKey ->
matchTypeSink.value =
DSecret.Uri.MatchType.valueOf(newMatchTypeKey)
matchDetectionSink.value = MatchDetection.valueOf(newMatchTypeKey)
}
navigate(intent)
},
@ -1056,6 +1065,7 @@ class AddStateItemUriFactory(
}
}
val defaultMatchDetection = getAutofillDefaultMatchDetection()
val textFlow = uriSink
.map { uri ->
TextFieldModel2(
@ -1066,10 +1076,12 @@ class AddStateItemUriFactory(
}
val stateFlow = combine(
textFlow,
matchTypeSink,
matchDetectionSink,
defaultMatchDetection,
actionsFlow,
) { text, matchType, actions ->
val badge = when (matchType) {
) { text, matchDetection, defaultMatchDetection, actions ->
val matchType = matchDetection.matchType
val badge = when (matchType ?: defaultMatchDetection) {
DSecret.Uri.MatchType.Never,
DSecret.Uri.MatchType.Host,
DSecret.Uri.MatchType.Domain,
@ -1110,7 +1122,6 @@ class AddStateItemUriFactory(
text = text.copy(vl = badge),
matchType = matchType,
matchTypeTitle = matchType
.takeIf { it != DSecret.Uri.MatchType.default }
?.let {
val name = translate(it.titleH())
name
@ -1122,7 +1133,7 @@ class AddStateItemUriFactory(
started = SharingStarted.WhileSubscribed(),
initialValue = AddStateItem.Url.State(
text = TextFieldModel2.empty,
matchType = DSecret.Uri.MatchType.default,
matchType = null,
),
)
return AddStateItem.Url<CreateRequest>(

View File

@ -2172,7 +2172,6 @@ private suspend fun RememberStateFlowScope.createUriItem(
val uri = holder.uri
val matchTypeTitle = holder.uri.match
.takeUnless { it == DSecret.Uri.MatchType.default }
?.titleH()
?.let {
translate(it)

View File

@ -14,11 +14,8 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.dp
import com.artemchep.keyguard.android.downloader.journal.room.DownloadInfoEntity2
import com.artemchep.keyguard.common.io.attempt
import com.artemchep.keyguard.common.io.bind
import com.artemchep.keyguard.common.io.ioRaise
import com.artemchep.keyguard.common.io.ioUnit
import com.artemchep.keyguard.common.model.BarcodeImageFormat
import com.artemchep.keyguard.common.model.DAccount
import com.artemchep.keyguard.common.model.DSecret
@ -28,20 +25,9 @@ import com.artemchep.keyguard.common.service.clipboard.ClipboardService
import com.artemchep.keyguard.common.service.download.DownloadManager
import com.artemchep.keyguard.common.service.extract.LinkInfoExtractor
import com.artemchep.keyguard.common.service.twofa.TwoFaService
import com.artemchep.keyguard.common.usecase.AddCipherOpenedHistory
import com.artemchep.keyguard.common.usecase.ChangeCipherNameById
import com.artemchep.keyguard.common.usecase.ChangeCipherPasswordById
import com.artemchep.keyguard.common.usecase.CipherExpiringCheck
import com.artemchep.keyguard.common.usecase.CipherFieldSwitchToggle
import com.artemchep.keyguard.common.usecase.CipherIncompleteCheck
import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlAutoFix
import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlCheck
import com.artemchep.keyguard.common.usecase.CipherUrlCheck
import com.artemchep.keyguard.common.usecase.CopyCipherById
import com.artemchep.keyguard.common.usecase.CopyText
import com.artemchep.keyguard.common.usecase.DateFormatter
import com.artemchep.keyguard.common.usecase.DownloadAttachment
import com.artemchep.keyguard.common.usecase.FavouriteCipherById
import com.artemchep.keyguard.common.usecase.GetAccounts
import com.artemchep.keyguard.common.usecase.GetAppIcons
import com.artemchep.keyguard.common.usecase.GetCanWrite
@ -55,22 +41,13 @@ import com.artemchep.keyguard.common.usecase.GetOrganizations
import com.artemchep.keyguard.common.usecase.GetPasswordStrength
import com.artemchep.keyguard.common.usecase.GetSends
import com.artemchep.keyguard.common.usecase.GetWebsiteIcons
import com.artemchep.keyguard.common.usecase.MoveCipherToFolderById
import com.artemchep.keyguard.common.usecase.PatchSendById
import com.artemchep.keyguard.common.usecase.RemoveAttachment
import com.artemchep.keyguard.common.usecase.RemoveCipherById
import com.artemchep.keyguard.common.usecase.RemoveSendById
import com.artemchep.keyguard.common.usecase.RestoreCipherById
import com.artemchep.keyguard.common.usecase.RetryCipher
import com.artemchep.keyguard.common.usecase.SendToolbox
import com.artemchep.keyguard.common.usecase.TrashCipherById
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.feature.attachments.model.AttachmentItem
import com.artemchep.keyguard.feature.attachments.util.createAttachmentItem
import com.artemchep.keyguard.feature.barcodetype.BarcodeTypeRoute
import com.artemchep.keyguard.feature.favicon.FaviconImage
import com.artemchep.keyguard.feature.favicon.FaviconUrl
import com.artemchep.keyguard.feature.filepicker.humanReadableByteCountSI
import com.artemchep.keyguard.feature.home.vault.model.VaultViewItem
import com.artemchep.keyguard.feature.largetype.LargeTypeRoute
import com.artemchep.keyguard.feature.localization.TextHolder
@ -92,13 +69,9 @@ import com.artemchep.keyguard.ui.buildContextItems
import com.artemchep.keyguard.ui.icons.ChevronIcon
import com.artemchep.keyguard.ui.icons.IconBox
import com.artemchep.keyguard.ui.icons.KeyguardView
import com.artemchep.keyguard.ui.selection.SelectionHandle
import com.artemchep.keyguard.ui.selection.selectionHandle
import com.artemchep.keyguard.ui.text.annotate
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
import io.ktor.http.Url
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
@ -133,16 +106,11 @@ fun sendViewScreenState(
toolbox = instance(),
downloadManager = instance(),
downloadAttachment = instance(),
removeAttachment = instance(),
cipherExpiringCheck = instance(),
cipherIncompleteCheck = instance(),
cipherUrlCheck = instance(),
tfaService = instance(),
clipboardService = instance(),
getGravatarUrl = instance(),
getEnvSendUrl = instance(),
dateFormatter = instance(),
addCipherOpenedHistory = instance(),
windowCoroutineScope = instance(),
linkInfoExtractors = allInstances(),
contentColor = contentColor,
@ -176,16 +144,11 @@ fun sendViewScreenState(
toolbox: SendToolbox,
downloadManager: DownloadManager,
downloadAttachment: DownloadAttachment,
removeAttachment: RemoveAttachment,
cipherExpiringCheck: CipherExpiringCheck,
cipherIncompleteCheck: CipherIncompleteCheck,
cipherUrlCheck: CipherUrlCheck,
tfaService: TwoFaService,
clipboardService: ClipboardService,
getGravatarUrl: GetGravatarUrl,
getEnvSendUrl: GetEnvSendUrl,
dateFormatter: DateFormatter,
addCipherOpenedHistory: AddCipherOpenedHistory,
windowCoroutineScope: WindowCoroutineScope,
linkInfoExtractors: List<LinkInfoExtractor<LinkInfo, LinkInfo>>,
sendId: String,

View File

@ -526,18 +526,15 @@ private suspend fun BitwardenCipher.Login.Companion.of(
return@mapNotNull null
}
val match = uri.match
.takeUnless { it == DSecret.Uri.MatchType.default }
?.let {
when (it) {
DSecret.Uri.MatchType.Domain -> BitwardenCipher.Login.Uri.MatchType.Domain
DSecret.Uri.MatchType.Host -> BitwardenCipher.Login.Uri.MatchType.Host
DSecret.Uri.MatchType.StartsWith -> BitwardenCipher.Login.Uri.MatchType.StartsWith
DSecret.Uri.MatchType.Exact -> BitwardenCipher.Login.Uri.MatchType.Exact
DSecret.Uri.MatchType.RegularExpression -> BitwardenCipher.Login.Uri.MatchType.RegularExpression
DSecret.Uri.MatchType.Never -> BitwardenCipher.Login.Uri.MatchType.Never
}
}
val match = when (uri.match) {
null -> null
DSecret.Uri.MatchType.Domain -> BitwardenCipher.Login.Uri.MatchType.Domain
DSecret.Uri.MatchType.Host -> BitwardenCipher.Login.Uri.MatchType.Host
DSecret.Uri.MatchType.StartsWith -> BitwardenCipher.Login.Uri.MatchType.StartsWith
DSecret.Uri.MatchType.Exact -> BitwardenCipher.Login.Uri.MatchType.Exact
DSecret.Uri.MatchType.RegularExpression -> BitwardenCipher.Login.Uri.MatchType.RegularExpression
DSecret.Uri.MatchType.Never -> BitwardenCipher.Login.Uri.MatchType.Never
}
BitwardenCipher.Login.Uri(
uri = uri.uri,
match = match,

View File

@ -12,11 +12,13 @@ import com.artemchep.keyguard.common.model.AddUriCipherRequest
import com.artemchep.keyguard.common.model.DSecret
import com.artemchep.keyguard.common.usecase.AddUriCipher
import com.artemchep.keyguard.common.usecase.CipherUrlDuplicateCheck
import com.artemchep.keyguard.common.usecase.GetAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.isEmpty
import com.artemchep.keyguard.common.util.Browsers
import com.artemchep.keyguard.core.store.bitwarden.BitwardenCipher
import com.artemchep.keyguard.provider.bitwarden.mapper.toDomain
import com.artemchep.keyguard.provider.bitwarden.usecase.util.ModifyCipherById
import kotlinx.coroutines.flow.first
import org.kodein.di.DirectDI
import org.kodein.di.instance
@ -25,6 +27,7 @@ import org.kodein.di.instance
*/
class AddUriCipherImpl(
private val modifyCipherById: ModifyCipherById,
private val getAutofillDefaultMatchDetection: GetAutofillDefaultMatchDetection,
private val cipherUrlDuplicateCheck: CipherUrlDuplicateCheck,
) : AddUriCipher {
companion object {
@ -33,6 +36,7 @@ class AddUriCipherImpl(
constructor(directDI: DirectDI) : this(
modifyCipherById = directDI.instance(),
getAutofillDefaultMatchDetection = directDI.instance(),
cipherUrlDuplicateCheck = directDI.instance(),
)
@ -43,6 +47,8 @@ class AddUriCipherImpl(
return@ioEffect io(false)
}
val defaultMatchDetection = getAutofillDefaultMatchDetection()
.first()
modifyCipherById(
setOf(request.cipherId),
) { model ->
@ -71,7 +77,11 @@ class AddUriCipherImpl(
val newUriDomain = newUri.toDomain()
oldUrisDomain
.none { oldUriDomain ->
val isDuplicate = cipherUrlDuplicateCheck(oldUriDomain, newUriDomain)
val isDuplicate = cipherUrlDuplicateCheck(
oldUriDomain,
newUriDomain,
defaultMatchDetection,
)
.attempt()
.bind()
.isRight { it != null }
@ -122,7 +132,6 @@ fun List<DSecret.Uri>.autofill(
if (!androidAppUriExists) {
existingUris += DSecret.Uri(
uri = androidAppUri,
match = DSecret.Uri.MatchType.default,
)
}
}
@ -135,7 +144,6 @@ fun List<DSecret.Uri>.autofill(
val webUri = "$webPrefix$webDomain"
existingUris += DSecret.Uri(
uri = webUri,
match = DSecret.Uri.MatchType.default,
)
}
}

View File

@ -50,6 +50,7 @@ class CipherBreachCheckImpl(
override fun invoke(
cipher: DSecret,
breaches: HibpBreachGroup,
defaultMatchDetection: DSecret.Uri.MatchType,
): IO<Boolean> = ioEffect {
val login = cipher.login
?: return@ioEffect false
@ -78,7 +79,7 @@ class CipherBreachCheckImpl(
?.takeIf { it.isNotBlank() }
?: return@filter false
cipher.uris.any { uri ->
cipherUrlCheck(uri, domain)
cipherUrlCheck(uri, domain, defaultMatchDetection)
.bind()
}
}

View File

@ -31,8 +31,9 @@ class CipherUrlCheckImpl(
override fun invoke(
uri: DSecret.Uri,
url: String,
defaultMatchDetection: DSecret.Uri.MatchType,
): IO<Boolean> {
return when (uri.match ?: DSecret.Uri.MatchType.default) {
return when (uri.match ?: defaultMatchDetection) {
DSecret.Uri.MatchType.Domain -> {
when {
uri.uri.startsWith(PROTOCOL_ANDROID_APP) -> ::checkUrlMatchByHost

View File

@ -22,9 +22,10 @@ class CipherUrlDuplicateCheckImpl(
override fun invoke(
a: DSecret.Uri,
b: DSecret.Uri,
defaultMatchDetection: DSecret.Uri.MatchType,
): IO<DSecret.Uri?> {
val aMatch = a.match ?: DSecret.Uri.MatchType.default
val bMatch = b.match ?: DSecret.Uri.MatchType.default
val aMatch = a.match ?: defaultMatchDetection
val bMatch = b.match ?: defaultMatchDetection
// If one of the URIs are set to never match then
// ignore it, it can not be a duplicate.
@ -45,7 +46,7 @@ class CipherUrlDuplicateCheckImpl(
return io(result)
}
return cipherUrlCheck(a, b.uri)
return cipherUrlCheck(a, b.uri, defaultMatchDetection)
.effectMap { areMatching ->
a.takeIf { areMatching }
}

View File

@ -108,6 +108,7 @@ import com.artemchep.keyguard.common.usecase.GetAppVersion
import com.artemchep.keyguard.common.usecase.GetAppVersionCode
import com.artemchep.keyguard.common.usecase.GetAppVersionName
import com.artemchep.keyguard.common.usecase.GetAutofillCopyTotp
import com.artemchep.keyguard.common.usecase.GetAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.GetAutofillInlineSuggestions
import com.artemchep.keyguard.common.usecase.GetAutofillManualSelection
import com.artemchep.keyguard.common.usecase.GetAutofillRespectAutofillOff
@ -177,6 +178,7 @@ import com.artemchep.keyguard.common.usecase.PutAllowTwoPanelLayoutInLandscape
import com.artemchep.keyguard.common.usecase.PutAllowTwoPanelLayoutInPortrait
import com.artemchep.keyguard.common.usecase.PutAppIcons
import com.artemchep.keyguard.common.usecase.PutAutofillCopyTotp
import com.artemchep.keyguard.common.usecase.PutAutofillDefaultMatchDetection
import com.artemchep.keyguard.common.usecase.PutAutofillInlineSuggestions
import com.artemchep.keyguard.common.usecase.PutAutofillManualSelection
import com.artemchep.keyguard.common.usecase.PutAutofillRespectAutofillOff
@ -242,6 +244,7 @@ import com.artemchep.keyguard.common.usecase.impl.GetAppVersionCodeImpl
import com.artemchep.keyguard.common.usecase.impl.GetAppVersionImpl
import com.artemchep.keyguard.common.usecase.impl.GetAppVersionNameImpl
import com.artemchep.keyguard.common.usecase.impl.GetAutofillCopyTotpImpl
import com.artemchep.keyguard.common.usecase.impl.GetAutofillDefaultMatchDetectionImpl
import com.artemchep.keyguard.common.usecase.impl.GetAutofillInlineSuggestionsImpl
import com.artemchep.keyguard.common.usecase.impl.GetAutofillManualSelectionImpl
import com.artemchep.keyguard.common.usecase.impl.GetAutofillRespectAutofillOffImpl
@ -311,6 +314,7 @@ import com.artemchep.keyguard.common.usecase.impl.PutAllowTwoPanelLayoutInLandsc
import com.artemchep.keyguard.common.usecase.impl.PutAllowTwoPanelLayoutInPortraitImpl
import com.artemchep.keyguard.common.usecase.impl.PutAppIconsImpl
import com.artemchep.keyguard.common.usecase.impl.PutAutofillCopyTotpImpl
import com.artemchep.keyguard.common.usecase.impl.PutAutofillDefaultMatchDetectionImpl
import com.artemchep.keyguard.common.usecase.impl.PutAutofillInlineSuggestionsImpl
import com.artemchep.keyguard.common.usecase.impl.PutAutofillManualSelectionImpl
import com.artemchep.keyguard.common.usecase.impl.PutAutofillRespectAutofillOffImpl
@ -550,6 +554,11 @@ fun globalModuleJvm() = DI.Module(
directDI = this,
)
}
bindSingleton<GetAutofillDefaultMatchDetection> {
GetAutofillDefaultMatchDetectionImpl(
directDI = this,
)
}
bindSingleton<GetAutofillInlineSuggestions> {
GetAutofillInlineSuggestionsImpl(
directDI = this,
@ -738,6 +747,11 @@ fun globalModuleJvm() = DI.Module(
directDI = this,
)
}
bindSingleton<PutAutofillDefaultMatchDetection> {
PutAutofillDefaultMatchDetectionImpl(
directDI = this,
)
}
bindSingleton<PutAutofillInlineSuggestions> {
PutAutofillInlineSuggestionsImpl(
directDI = this,