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 getKeepScreenOn(): Flow<Boolean>
fun getGravatar(): Flow<Boolean>
fun getAllowTwoPanelLayoutInPortrait(): Flow<Boolean> fun getAllowTwoPanelLayoutInPortrait(): Flow<Boolean>
fun getAllowTwoPanelLayoutInLandscape(): Flow<Boolean> fun getAllowTwoPanelLayoutInLandscape(): Flow<Boolean>

View File

@ -149,6 +149,10 @@ interface SettingsReadWriteRepository : SettingsReadRepository {
keepScreenOn: Boolean, keepScreenOn: Boolean,
): IO<Unit> ): IO<Unit>
fun setGravatar(
enabled: Boolean,
): IO<Unit>
fun setAllowTwoPanelLayoutInPortrait( fun setAllowTwoPanelLayoutInPortrait(
allow: Boolean, allow: Boolean,
): IO<Unit> ): 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_THEME_USE_AMOLED_DARK = "theme_use_amoled_dark"
private const val KEY_ONBOARDING_LAST_VISIT = "onboarding_last_visit" private const val KEY_ONBOARDING_LAST_VISIT = "onboarding_last_visit"
private const val KEY_KEEP_SCREEN_ON = "keep_screen_on" 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_COLORS = "colors"
private const val KEY_LOCALE = "locale" private const val KEY_LOCALE = "locale"
@ -158,6 +159,9 @@ class SettingsRepositoryImpl(
private val keepScreenOnPref = private val keepScreenOnPref =
store.getBoolean(KEY_KEEP_SCREEN_ON, true) store.getBoolean(KEY_KEEP_SCREEN_ON, true)
private val gravatarPref =
store.getBoolean(KEY_GRAVATAR, true)
private val navLabelPref = private val navLabelPref =
store.getBoolean(KEY_NAV_LABEL, true) store.getBoolean(KEY_NAV_LABEL, true)
@ -409,6 +413,11 @@ class SettingsRepositoryImpl(
override fun getKeepScreenOn() = keepScreenOnPref override fun getKeepScreenOn() = keepScreenOnPref
override fun setGravatar(enabled: Boolean) = gravatarPref
.setAndCommit(enabled)
override fun getGravatar() = gravatarPref
override fun setAllowTwoPanelLayoutInLandscape(allow: Boolean) = override fun setAllowTwoPanelLayoutInLandscape(allow: Boolean) =
allowTwoPanelLayoutInLandscapePref allowTwoPanelLayoutInLandscapePref
.setAndCommit(allow) .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.IO
import com.artemchep.keyguard.common.io.ioEffect import com.artemchep.keyguard.common.io.ioEffect
import com.artemchep.keyguard.common.service.crypto.CryptoGenerator 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.common.usecase.GetGravatarUrl
import com.artemchep.keyguard.feature.favicon.GravatarUrl import com.artemchep.keyguard.feature.favicon.GravatarUrl
import io.ktor.util.hex import io.ktor.util.hex
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import org.kodein.di.DirectDI import org.kodein.di.DirectDI
import org.kodein.di.instance import org.kodein.di.instance
import java.util.Locale import java.util.Locale
class GetGravatarUrlImpl( class GetGravatarUrlImpl(
private val cryptoGenerator: CryptoGenerator, private val cryptoGenerator: CryptoGenerator,
private val getGravatar: GetGravatar,
) : GetGravatarUrl { ) : GetGravatarUrl {
private val emailPlusAddressingRegex = "\\+.+(?=@)".toRegex() private val emailPlusAddressingRegex = "\\+.+(?=@)".toRegex()
class GravatarDisabledException : RuntimeException()
constructor(directDI: DirectDI) : this( constructor(directDI: DirectDI) : this(
cryptoGenerator = directDI.instance(), cryptoGenerator = directDI.instance(),
getGravatar = directDI.instance(),
) )
override fun invoke( override fun invoke(
email: String, email: String,
): IO<GravatarUrl> = ioEffect(Dispatchers.Default) { ): IO<GravatarUrl> = ioEffect(Dispatchers.Default) {
val gravatarEnabled = getGravatar()
.first()
if (!gravatarEnabled) {
throw GravatarDisabledException()
}
val emailHash = run { val emailHash = run {
// https://en.gravatar.com/site/implement/hash/ // https://en.gravatar.com/site/implement/hash/
val sanitizedEmail = transformEmail(email) 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.settingFeedbackAppProvider
import com.artemchep.keyguard.feature.home.settings.component.settingFontProvider 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.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.settingKeepScreenOnProvider
import com.artemchep.keyguard.feature.home.settings.component.settingLaunchAppPicker import com.artemchep.keyguard.feature.home.settings.component.settingLaunchAppPicker
import com.artemchep.keyguard.feature.home.settings.component.settingLaunchYubiKey import com.artemchep.keyguard.feature.home.settings.component.settingLaunchYubiKey
@ -150,6 +151,7 @@ object Setting {
const val REDDIT = "reddit" const val REDDIT = "reddit"
const val CROWDIN = "crowdin" const val CROWDIN = "crowdin"
const val GITHUB = "github" const val GITHUB = "github"
const val GRAVATAR = "gravatar"
const val PRIVACY_POLICY = "privacy_policy" const val PRIVACY_POLICY = "privacy_policy"
const val OPEN_SOURCE_LICENSES = "open_source_licenses" const val OPEN_SOURCE_LICENSES = "open_source_licenses"
const val ABOUT_APP = "about_app" const val ABOUT_APP = "about_app"
@ -237,6 +239,7 @@ val hub = mapOf<String, (DirectDI) -> SettingComponent>(
Setting.REDDIT to ::settingAboutTelegramProvider, Setting.REDDIT to ::settingAboutTelegramProvider,
Setting.CROWDIN to ::settingLocalizationProvider, Setting.CROWDIN to ::settingLocalizationProvider,
Setting.GITHUB to ::settingGitHubProvider, Setting.GITHUB to ::settingGitHubProvider,
Setting.GRAVATAR to ::settingGravatarProvider,
Setting.PRIVACY_POLICY to ::settingPrivacyPolicyProvider, Setting.PRIVACY_POLICY to ::settingPrivacyPolicyProvider,
Setting.OPEN_SOURCE_LICENSES to ::settingOpenSourceLicensesProvider, Setting.OPEN_SOURCE_LICENSES to ::settingOpenSourceLicensesProvider,
Setting.EXPERIMENTAL to ::settingExperimentalProvider, 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( list = listOf(
SettingPaneItem.Item(Setting.APP_ICONS), SettingPaneItem.Item(Setting.APP_ICONS),
SettingPaneItem.Item(Setting.WEBSITE_ICONS), SettingPaneItem.Item(Setting.WEBSITE_ICONS),
SettingPaneItem.Item(Setting.GRAVATAR),
), ),
), ),
SettingPaneItem.Group( SettingPaneItem.Group(

View File

@ -30,6 +30,7 @@ fun SecuritySettingsScreen() {
SettingPaneItem.Item(Setting.CONCEAL), SettingPaneItem.Item(Setting.CONCEAL),
SettingPaneItem.Item(Setting.SCREENSHOTS), SettingPaneItem.Item(Setting.SCREENSHOTS),
SettingPaneItem.Item(Setting.WEBSITE_ICONS), SettingPaneItem.Item(Setting.WEBSITE_ICONS),
SettingPaneItem.Item(Setting.GRAVATAR),
), ),
), ),
SettingPaneItem.Group( 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_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_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_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_title">Rich text formatting</string>
<string name="pref_item_markdown_text">Use Markdown to format notes</string> <string name="pref_item_markdown_text">Use Markdown to format notes</string>
<string name="pref_item_locale_title">Language</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.GetDebugScreenDelay
import com.artemchep.keyguard.common.usecase.GetFont import com.artemchep.keyguard.common.usecase.GetFont
import com.artemchep.keyguard.common.usecase.GetFontVariants 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.GetGravatarUrl
import com.artemchep.keyguard.common.usecase.GetJustDeleteMeByUrl import com.artemchep.keyguard.common.usecase.GetJustDeleteMeByUrl
import com.artemchep.keyguard.common.usecase.GetJustGetMyDataByUrl 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.PutDebugPremium
import com.artemchep.keyguard.common.usecase.PutDebugScreenDelay import com.artemchep.keyguard.common.usecase.PutDebugScreenDelay
import com.artemchep.keyguard.common.usecase.PutFont 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.PutKeepScreenOn
import com.artemchep.keyguard.common.usecase.PutMarkdown import com.artemchep.keyguard.common.usecase.PutMarkdown
import com.artemchep.keyguard.common.usecase.PutNavAnimation 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.GetDebugScreenDelayImpl
import com.artemchep.keyguard.common.usecase.impl.GetFontImpl import com.artemchep.keyguard.common.usecase.impl.GetFontImpl
import com.artemchep.keyguard.common.usecase.impl.GetFontVariantsImpl 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.GetGravatarUrlImpl
import com.artemchep.keyguard.common.usecase.impl.GetJustDeleteMeByUrlImpl import com.artemchep.keyguard.common.usecase.impl.GetJustDeleteMeByUrlImpl
import com.artemchep.keyguard.common.usecase.impl.GetJustGetMyDataByUrlImpl 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.PutDebugPremiumImpl
import com.artemchep.keyguard.common.usecase.impl.PutDebugScreenDelayImpl import com.artemchep.keyguard.common.usecase.impl.PutDebugScreenDelayImpl
import com.artemchep.keyguard.common.usecase.impl.PutFontImpl 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.PutKeepScreenOnImpl
import com.artemchep.keyguard.common.usecase.impl.PutMarkdownImpl import com.artemchep.keyguard.common.usecase.impl.PutMarkdownImpl
import com.artemchep.keyguard.common.usecase.impl.PutNavAnimationImpl import com.artemchep.keyguard.common.usecase.impl.PutNavAnimationImpl
@ -865,6 +869,16 @@ fun globalModuleJvm() = DI.Module(
directDI = this, directDI = this,
) )
} }
bindSingleton<GetGravatar> {
GetGravatarImpl(
directDI = this,
)
}
bindSingleton<PutGravatar> {
PutGravatarImpl(
directDI = this,
)
}
bindSingleton<GetJustDeleteMeByUrl> { bindSingleton<GetJustDeleteMeByUrl> {
GetJustDeleteMeByUrlImpl( GetJustDeleteMeByUrlImpl(
directDI = this, directDI = this,