feat: Allow disabling Gravatar icons

This commit is contained in:
Artem Chepurnoy 2024-04-10 12:21:23 +03:00
parent b8cd84a7f4
commit 8f79db1fe7
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
14 changed files with 184 additions and 0 deletions

View File

@ -81,6 +81,8 @@ interface SettingsReadRepository {
fun getKeepScreenOn(): Flow<Boolean>
fun getGravatar(): Flow<Boolean>
fun getAllowTwoPanelLayoutInPortrait(): Flow<Boolean>
fun getAllowTwoPanelLayoutInLandscape(): Flow<Boolean>

View File

@ -149,6 +149,10 @@ interface SettingsReadWriteRepository : SettingsReadRepository {
keepScreenOn: Boolean,
): IO<Unit>
fun setGravatar(
enabled: Boolean,
): IO<Unit>
fun setAllowTwoPanelLayoutInPortrait(
allow: Boolean,
): IO<Unit>

View File

@ -74,6 +74,7 @@ class SettingsRepositoryImpl(
private const val KEY_THEME_USE_AMOLED_DARK = "theme_use_amoled_dark"
private const val KEY_ONBOARDING_LAST_VISIT = "onboarding_last_visit"
private const val KEY_KEEP_SCREEN_ON = "keep_screen_on"
private const val KEY_GRAVATAR = "gravatar"
private const val KEY_COLORS = "colors"
private const val KEY_LOCALE = "locale"
@ -158,6 +159,9 @@ class SettingsRepositoryImpl(
private val keepScreenOnPref =
store.getBoolean(KEY_KEEP_SCREEN_ON, true)
private val gravatarPref =
store.getBoolean(KEY_GRAVATAR, true)
private val navLabelPref =
store.getBoolean(KEY_NAV_LABEL, true)
@ -409,6 +413,11 @@ class SettingsRepositoryImpl(
override fun getKeepScreenOn() = keepScreenOnPref
override fun setGravatar(enabled: Boolean) = gravatarPref
.setAndCommit(enabled)
override fun getGravatar() = gravatarPref
override fun setAllowTwoPanelLayoutInLandscape(allow: Boolean) =
allowTwoPanelLayoutInLandscapePref
.setAndCommit(allow)

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.usecase
import kotlinx.coroutines.flow.Flow
interface GetGravatar : () -> Flow<Boolean>

View File

@ -0,0 +1,5 @@
package com.artemchep.keyguard.common.usecase
import com.artemchep.keyguard.common.io.IO
interface PutGravatar : (Boolean) -> IO<Unit>

View File

@ -0,0 +1,21 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.service.settings.SettingsReadRepository
import com.artemchep.keyguard.common.usecase.GetGravatar
import com.artemchep.keyguard.common.usecase.GetKeepScreenOn
import kotlinx.coroutines.flow.distinctUntilChanged
import org.kodein.di.DirectDI
import org.kodein.di.instance
class GetGravatarImpl(
settingsReadRepository: SettingsReadRepository,
) : GetGravatar {
private val sharedFlow = settingsReadRepository.getGravatar()
.distinctUntilChanged()
constructor(directDI: DirectDI) : this(
settingsReadRepository = directDI.instance(),
)
override fun invoke() = sharedFlow
}

View File

@ -3,26 +3,38 @@ package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.ioEffect
import com.artemchep.keyguard.common.service.crypto.CryptoGenerator
import com.artemchep.keyguard.common.usecase.GetGravatar
import com.artemchep.keyguard.common.usecase.GetGravatarUrl
import com.artemchep.keyguard.feature.favicon.GravatarUrl
import io.ktor.util.hex
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import org.kodein.di.DirectDI
import org.kodein.di.instance
import java.util.Locale
class GetGravatarUrlImpl(
private val cryptoGenerator: CryptoGenerator,
private val getGravatar: GetGravatar,
) : GetGravatarUrl {
private val emailPlusAddressingRegex = "\\+.+(?=@)".toRegex()
class GravatarDisabledException : RuntimeException()
constructor(directDI: DirectDI) : this(
cryptoGenerator = directDI.instance(),
getGravatar = directDI.instance(),
)
override fun invoke(
email: String,
): IO<GravatarUrl> = ioEffect(Dispatchers.Default) {
val gravatarEnabled = getGravatar()
.first()
if (!gravatarEnabled) {
throw GravatarDisabledException()
}
val emailHash = run {
// https://en.gravatar.com/site/implement/hash/
val sanitizedEmail = transformEmail(email)

View File

@ -0,0 +1,18 @@
package com.artemchep.keyguard.common.usecase.impl
import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.service.settings.SettingsReadWriteRepository
import com.artemchep.keyguard.common.usecase.PutGravatar
import org.kodein.di.DirectDI
import org.kodein.di.instance
class PutGravatarImpl(
private val settingsReadWriteRepository: SettingsReadWriteRepository,
) : PutGravatar {
constructor(directDI: DirectDI) : this(
settingsReadWriteRepository = directDI.instance(),
)
override fun invoke(enabled: Boolean): IO<Unit> = settingsReadWriteRepository
.setGravatar(enabled)
}

View File

@ -57,6 +57,7 @@ import com.artemchep.keyguard.feature.home.settings.component.settingFeaturesOve
import com.artemchep.keyguard.feature.home.settings.component.settingFeedbackAppProvider
import com.artemchep.keyguard.feature.home.settings.component.settingFontProvider
import com.artemchep.keyguard.feature.home.settings.component.settingGitHubProvider
import com.artemchep.keyguard.feature.home.settings.component.settingGravatarProvider
import com.artemchep.keyguard.feature.home.settings.component.settingKeepScreenOnProvider
import com.artemchep.keyguard.feature.home.settings.component.settingLaunchAppPicker
import com.artemchep.keyguard.feature.home.settings.component.settingLaunchYubiKey
@ -150,6 +151,7 @@ object Setting {
const val REDDIT = "reddit"
const val CROWDIN = "crowdin"
const val GITHUB = "github"
const val GRAVATAR = "gravatar"
const val PRIVACY_POLICY = "privacy_policy"
const val OPEN_SOURCE_LICENSES = "open_source_licenses"
const val ABOUT_APP = "about_app"
@ -237,6 +239,7 @@ val hub = mapOf<String, (DirectDI) -> SettingComponent>(
Setting.REDDIT to ::settingAboutTelegramProvider,
Setting.CROWDIN to ::settingLocalizationProvider,
Setting.GITHUB to ::settingGitHubProvider,
Setting.GRAVATAR to ::settingGravatarProvider,
Setting.PRIVACY_POLICY to ::settingPrivacyPolicyProvider,
Setting.OPEN_SOURCE_LICENSES to ::settingOpenSourceLicensesProvider,
Setting.EXPERIMENTAL to ::settingExperimentalProvider,

View File

@ -0,0 +1,88 @@
package com.artemchep.keyguard.feature.home.settings.component
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccountCircle
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import arrow.core.partially1
import com.artemchep.keyguard.common.io.launchIn
import com.artemchep.keyguard.common.usecase.GetGravatar
import com.artemchep.keyguard.common.usecase.PutGravatar
import com.artemchep.keyguard.common.usecase.WindowCoroutineScope
import com.artemchep.keyguard.res.Res
import com.artemchep.keyguard.ui.FlatItem
import compose.icons.FeatherIcons
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.map
import org.kodein.di.DirectDI
import org.kodein.di.instance
fun settingGravatarProvider(
directDI: DirectDI,
) = settingGravatarProvider(
getGravatar = directDI.instance(),
putGravatar = directDI.instance(),
windowCoroutineScope = directDI.instance(),
)
fun settingGravatarProvider(
getGravatar: GetGravatar,
putGravatar: PutGravatar,
windowCoroutineScope: WindowCoroutineScope,
): SettingComponent = getGravatar().map { gravatar ->
val onCheckedChange = { shouldBeEnabled: Boolean ->
putGravatar(shouldBeEnabled)
.launchIn(windowCoroutineScope)
Unit
}
SettingIi(
search = SettingIi.Search(
group = "ui",
tokens = listOf(
"gravatar",
),
),
) {
SettingGravatar(
checked = gravatar,
onCheckedChange = onCheckedChange,
)
}
}
@Composable
private fun SettingGravatar(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
) {
FlatItem(
leading = {
Icon(
imageVector = Icons.Outlined.AccountCircle,
contentDescription = null,
)
},
trailing = {
CompositionLocalProvider(
LocalMinimumInteractiveComponentEnforcement provides false,
) {
Switch(
checked = checked,
enabled = onCheckedChange != null,
onCheckedChange = onCheckedChange,
)
}
},
title = {
Text(
text = stringResource(Res.strings.pref_item_load_gravatar_icons_title),
)
},
onClick = onCheckedChange?.partially1(!checked),
)
}

View File

@ -35,6 +35,7 @@ fun UiSettingsScreen() {
list = listOf(
SettingPaneItem.Item(Setting.APP_ICONS),
SettingPaneItem.Item(Setting.WEBSITE_ICONS),
SettingPaneItem.Item(Setting.GRAVATAR),
),
),
SettingPaneItem.Group(

View File

@ -30,6 +30,7 @@ fun SecuritySettingsScreen() {
SettingPaneItem.Item(Setting.CONCEAL),
SettingPaneItem.Item(Setting.SCREENSHOTS),
SettingPaneItem.Item(Setting.WEBSITE_ICONS),
SettingPaneItem.Item(Setting.GRAVATAR),
),
),
SettingPaneItem.Group(

View File

@ -966,6 +966,7 @@
<string name="pref_item_load_app_icons_title">Load App icons</string>
<string name="pref_item_load_website_icons_title">Load Website icons</string>
<string name="pref_item_load_website_icons_text">Queuing website icon might leak the website address to internet provider</string>
<string name="pref_item_load_gravatar_icons_title">Load Gravatar icons</string>
<string name="pref_item_markdown_title">Rich text formatting</string>
<string name="pref_item_markdown_text">Use Markdown to format notes</string>
<string name="pref_item_locale_title">Language</string>

View File

@ -120,6 +120,7 @@ import com.artemchep.keyguard.common.usecase.GetDebugPremium
import com.artemchep.keyguard.common.usecase.GetDebugScreenDelay
import com.artemchep.keyguard.common.usecase.GetFont
import com.artemchep.keyguard.common.usecase.GetFontVariants
import com.artemchep.keyguard.common.usecase.GetGravatar
import com.artemchep.keyguard.common.usecase.GetGravatarUrl
import com.artemchep.keyguard.common.usecase.GetJustDeleteMeByUrl
import com.artemchep.keyguard.common.usecase.GetJustGetMyDataByUrl
@ -177,6 +178,7 @@ import com.artemchep.keyguard.common.usecase.PutConcealFields
import com.artemchep.keyguard.common.usecase.PutDebugPremium
import com.artemchep.keyguard.common.usecase.PutDebugScreenDelay
import com.artemchep.keyguard.common.usecase.PutFont
import com.artemchep.keyguard.common.usecase.PutGravatar
import com.artemchep.keyguard.common.usecase.PutKeepScreenOn
import com.artemchep.keyguard.common.usecase.PutMarkdown
import com.artemchep.keyguard.common.usecase.PutNavAnimation
@ -244,6 +246,7 @@ import com.artemchep.keyguard.common.usecase.impl.GetDebugPremiumImpl
import com.artemchep.keyguard.common.usecase.impl.GetDebugScreenDelayImpl
import com.artemchep.keyguard.common.usecase.impl.GetFontImpl
import com.artemchep.keyguard.common.usecase.impl.GetFontVariantsImpl
import com.artemchep.keyguard.common.usecase.impl.GetGravatarImpl
import com.artemchep.keyguard.common.usecase.impl.GetGravatarUrlImpl
import com.artemchep.keyguard.common.usecase.impl.GetJustDeleteMeByUrlImpl
import com.artemchep.keyguard.common.usecase.impl.GetJustGetMyDataByUrlImpl
@ -298,6 +301,7 @@ import com.artemchep.keyguard.common.usecase.impl.PutConcealFieldsImpl
import com.artemchep.keyguard.common.usecase.impl.PutDebugPremiumImpl
import com.artemchep.keyguard.common.usecase.impl.PutDebugScreenDelayImpl
import com.artemchep.keyguard.common.usecase.impl.PutFontImpl
import com.artemchep.keyguard.common.usecase.impl.PutGravatarImpl
import com.artemchep.keyguard.common.usecase.impl.PutKeepScreenOnImpl
import com.artemchep.keyguard.common.usecase.impl.PutMarkdownImpl
import com.artemchep.keyguard.common.usecase.impl.PutNavAnimationImpl
@ -865,6 +869,16 @@ fun globalModuleJvm() = DI.Module(
directDI = this,
)
}
bindSingleton<GetGravatar> {
GetGravatarImpl(
directDI = this,
)
}
bindSingleton<PutGravatar> {
PutGravatarImpl(
directDI = this,
)
}
bindSingleton<GetJustDeleteMeByUrl> {
GetJustDeleteMeByUrlImpl(
directDI = this,