diff --git a/app/build.gradle b/app/build.gradle index aeb4054e6784..2fea05b45059 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -260,6 +260,7 @@ dependencies { implementation project(':common-utils') implementation project(':app-store') implementation project(':common-ui') + implementation project(':common-ui-experiments') internalImplementation project(':common-ui-internal') implementation project(':di') implementation project(':app-tracking-api') diff --git a/app/src/main/java/com/duckduckgo/app/appearance/AppearanceActivity.kt b/app/src/main/java/com/duckduckgo/app/appearance/AppearanceActivity.kt index d28006d64fdf..22119dc6ef82 100644 --- a/app/src/main/java/com/duckduckgo/app/appearance/AppearanceActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/appearance/AppearanceActivity.kt @@ -39,6 +39,11 @@ import com.duckduckgo.app.browser.omnibar.model.OmnibarPosition import com.duckduckgo.app.fire.FireActivity import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.DuckDuckGoTheme +import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK +import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK_EXPERIMENT +import com.duckduckgo.common.ui.DuckDuckGoTheme.LIGHT +import com.duckduckgo.common.ui.DuckDuckGoTheme.LIGHT_EXPERIMENT +import com.duckduckgo.common.ui.DuckDuckGoTheme.SYSTEM_DEFAULT import com.duckduckgo.common.ui.sendThemeChangedBroadcast import com.duckduckgo.common.ui.view.dialog.RadioListAlertDialogBuilder import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder @@ -82,6 +87,11 @@ class AppearanceActivity : DuckDuckGoActivity() { .show() } + private val experimentalUIToggleListener = CompoundButton.OnCheckedChangeListener { _, isChecked -> + viewModel.onExperimentalUIModeChanged(isChecked) + sendThemeChangedBroadcast() + } + private val changeIconFlow = registerForActivityResult(ChangeIconContract()) { resultOk -> if (resultOk) { Timber.d("Icon changed.") @@ -116,6 +126,7 @@ class AppearanceActivity : DuckDuckGoActivity() { binding.experimentalNightMode.isEnabled = viewState.canForceDarkMode binding.experimentalNightMode.isVisible = viewState.supportsForceDarkMode updateSelectedOmnibarPosition(it.isOmnibarPositionFeatureEnabled, it.omnibarPosition) + updateExperimentalUISetting(it.isBrowserThemingFeatureVisible, it.isBrowserThemingFeatureEnabled) } }.launchIn(lifecycleScope) @@ -128,9 +139,11 @@ class AppearanceActivity : DuckDuckGoActivity() { private fun updateSelectedTheme(selectedTheme: DuckDuckGoTheme) { val subtitle = getString( when (selectedTheme) { - DuckDuckGoTheme.DARK -> R.string.settingsDarkTheme - DuckDuckGoTheme.LIGHT -> R.string.settingsLightTheme - DuckDuckGoTheme.SYSTEM_DEFAULT -> R.string.settingsSystemTheme + DARK -> R.string.settingsDarkTheme + LIGHT -> R.string.settingsLightTheme + SYSTEM_DEFAULT -> R.string.settingsSystemTheme + DARK_EXPERIMENT -> R.string.settingsDarkTheme + LIGHT_EXPERIMENT -> R.string.settingsLightTheme }, ) binding.selectedThemeSetting.setSecondaryText(subtitle) @@ -153,6 +166,18 @@ class AppearanceActivity : DuckDuckGoActivity() { } } + private fun updateExperimentalUISetting( + browserThemingFeatureVisible: Boolean, + browserThemingFeatureEnabled: Boolean, + ) { + if (browserThemingFeatureVisible) { + binding.internalUISettingsLayout.show() + binding.experimentalUIMode.quietlySetIsChecked(browserThemingFeatureEnabled, experimentalUIToggleListener) + } else { + binding.internalUISettingsLayout.gone() + } + } + private fun processCommand(it: Command) { when (it) { is LaunchAppIcon -> launchAppIconChange() diff --git a/app/src/main/java/com/duckduckgo/app/appearance/AppearanceViewModel.kt b/app/src/main/java/com/duckduckgo/app/appearance/AppearanceViewModel.kt index fd0a74ba426d..a3f8b5a9abe3 100644 --- a/app/src/main/java/com/duckduckgo/app/appearance/AppearanceViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/appearance/AppearanceViewModel.kt @@ -16,6 +16,7 @@ package com.duckduckgo.app.appearance +import android.annotation.SuppressLint import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.webkit.WebViewFeature @@ -24,12 +25,24 @@ import com.duckduckgo.app.browser.omnibar.ChangeOmnibarPositionFeature import com.duckduckgo.app.browser.omnibar.model.OmnibarPosition import com.duckduckgo.app.icon.api.AppIcon import com.duckduckgo.app.pixels.AppPixelName +import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_THEME_TOGGLED_DARK +import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_THEME_TOGGLED_LIGHT +import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_THEME_TOGGLED_SYSTEM_DEFAULT import com.duckduckgo.app.settings.db.SettingsDataStore import com.duckduckgo.app.statistics.pixels.Pixel +import com.duckduckgo.appbuildconfig.api.AppBuildConfig +import com.duckduckgo.appbuildconfig.api.isInternalBuild import com.duckduckgo.common.ui.DuckDuckGoTheme +import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK +import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK_EXPERIMENT +import com.duckduckgo.common.ui.DuckDuckGoTheme.LIGHT +import com.duckduckgo.common.ui.DuckDuckGoTheme.LIGHT_EXPERIMENT +import com.duckduckgo.common.ui.DuckDuckGoTheme.SYSTEM_DEFAULT +import com.duckduckgo.common.ui.experiments.BrowserThemingFeature import com.duckduckgo.common.ui.store.ThemingDataStore import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.feature.toggles.api.Toggle.State import javax.inject.Inject import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel @@ -49,6 +62,8 @@ class AppearanceViewModel @Inject constructor( private val pixel: Pixel, private val dispatcherProvider: DispatcherProvider, private val changeOmnibarPositionFeature: ChangeOmnibarPositionFeature, + private val appBuildConfig: AppBuildConfig, + private val browserThemingFeature: BrowserThemingFeature, ) : ViewModel() { data class ViewState( @@ -59,6 +74,8 @@ class AppearanceViewModel @Inject constructor( val supportsForceDarkMode: Boolean = true, val omnibarPosition: OmnibarPosition = OmnibarPosition.TOP, val isOmnibarPositionFeatureEnabled: Boolean = true, + val isBrowserThemingFeatureVisible: Boolean = false, + val isBrowserThemingFeatureEnabled: Boolean = false, ) sealed class Command { @@ -82,6 +99,8 @@ class AppearanceViewModel @Inject constructor( supportsForceDarkMode = WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING), omnibarPosition = settingsDataStore.omnibarPosition, isOmnibarPositionFeatureEnabled = changeOmnibarPositionFeature.self().isEnabled(), + isBrowserThemingFeatureEnabled = browserThemingFeature.self().isEnabled(), + isBrowserThemingFeatureVisible = appBuildConfig.isInternalBuild(), ) } } @@ -126,9 +145,11 @@ class AppearanceViewModel @Inject constructor( val pixelName = when (selectedTheme) { - DuckDuckGoTheme.LIGHT -> AppPixelName.SETTINGS_THEME_TOGGLED_LIGHT - DuckDuckGoTheme.DARK -> AppPixelName.SETTINGS_THEME_TOGGLED_DARK - DuckDuckGoTheme.SYSTEM_DEFAULT -> AppPixelName.SETTINGS_THEME_TOGGLED_SYSTEM_DEFAULT + LIGHT -> SETTINGS_THEME_TOGGLED_LIGHT + DARK -> SETTINGS_THEME_TOGGLED_DARK + SYSTEM_DEFAULT -> SETTINGS_THEME_TOGGLED_SYSTEM_DEFAULT + DARK_EXPERIMENT -> SETTINGS_THEME_TOGGLED_DARK + LIGHT_EXPERIMENT -> SETTINGS_THEME_TOGGLED_LIGHT } pixel.fire(pixelName) } @@ -159,4 +180,12 @@ class AppearanceViewModel @Inject constructor( settingsDataStore.experimentalWebsiteDarkMode = checked } } + + @SuppressLint("DenyListedApi") + // only visible for UI Internal experiments + fun onExperimentalUIModeChanged(checked: Boolean) { + viewModelScope.launch(dispatcherProvider.io()) { + browserThemingFeature.self().setRawStoredState(State(checked)) + } + } } diff --git a/app/src/main/java/com/duckduckgo/app/feedback/ui/initial/InitialFeedbackFragment.kt b/app/src/main/java/com/duckduckgo/app/feedback/ui/initial/InitialFeedbackFragment.kt index c110fe0c58b7..7d3c680e6d0f 100644 --- a/app/src/main/java/com/duckduckgo/app/feedback/ui/initial/InitialFeedbackFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/feedback/ui/initial/InitialFeedbackFragment.kt @@ -25,7 +25,9 @@ import com.duckduckgo.app.browser.databinding.ContentFeedbackBinding import com.duckduckgo.app.feedback.ui.common.FeedbackFragment import com.duckduckgo.app.feedback.ui.initial.InitialFeedbackFragmentViewModel.Command.* import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK +import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK_EXPERIMENT import com.duckduckgo.common.ui.DuckDuckGoTheme.LIGHT +import com.duckduckgo.common.ui.DuckDuckGoTheme.LIGHT_EXPERIMENT import com.duckduckgo.common.ui.DuckDuckGoTheme.SYSTEM_DEFAULT import com.duckduckgo.common.ui.store.ThemingDataStore import com.duckduckgo.common.ui.viewbinding.viewBinding @@ -64,6 +66,8 @@ class InitialFeedbackFragment : FeedbackFragment(R.layout.content_feedback) { } DARK -> renderDarkButtons() LIGHT -> renderLightButtons() + DARK_EXPERIMENT -> renderDarkButtons() + LIGHT_EXPERIMENT -> renderLightButtons() } } diff --git a/app/src/main/res/layout/activity_appearance.xml b/app/src/main/res/layout/activity_appearance.xml index dced80dcb342..0d7af885e97f 100644 --- a/app/src/main/res/layout/activity_appearance.xml +++ b/app/src/main/res/layout/activity_appearance.xml @@ -102,6 +102,39 @@ app:primaryTextTruncated="false" app:secondaryText="@string/settingsAddressBarPositionTop" /> + + + + + + + + + + + + + diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 004f783907ee..efa290870cc6 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -76,4 +76,9 @@ The government may be blocking access to duckduckgo.com on this network provider, which could affect this app\'s functionality. Other providers may not be affected. Okay + + Experimental UI Settings + Enable visual design updates from O-A + This feature is in active development, intended for feedback purposes. + diff --git a/app/src/test/java/com/duckduckgo/app/appearance/AppearanceViewModelTest.kt b/app/src/test/java/com/duckduckgo/app/appearance/AppearanceViewModelTest.kt index 7ad3a645cc86..af8440fbb05a 100644 --- a/app/src/test/java/com/duckduckgo/app/appearance/AppearanceViewModelTest.kt +++ b/app/src/test/java/com/duckduckgo/app/appearance/AppearanceViewModelTest.kt @@ -16,6 +16,7 @@ package com.duckduckgo.app.appearance +import android.annotation.SuppressLint import androidx.arch.core.executor.testing.InstantTaskExecutorRule import app.cash.turbine.test import com.duckduckgo.app.appearance.AppearanceViewModel.Command @@ -27,8 +28,11 @@ import com.duckduckgo.app.pixels.AppPixelName import com.duckduckgo.app.settings.clear.FireAnimation import com.duckduckgo.app.settings.db.SettingsDataStore import com.duckduckgo.app.statistics.pixels.Pixel +import com.duckduckgo.appbuildconfig.api.AppBuildConfig +import com.duckduckgo.appbuildconfig.api.BuildFlavor.INTERNAL import com.duckduckgo.common.test.CoroutineTestRule import com.duckduckgo.common.ui.DuckDuckGoTheme +import com.duckduckgo.common.ui.experiments.BrowserThemingFeature import com.duckduckgo.common.ui.store.AppTheme import com.duckduckgo.common.ui.store.ThemingDataStore import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory @@ -66,8 +70,13 @@ internal class AppearanceViewModelTest { @Mock private lateinit var mockAppTheme: AppTheme - private val featureFlag = FakeFeatureToggleFactory.create(ChangeOmnibarPositionFeature::class.java) + @Mock + private lateinit var mockAppBuildConfig: AppBuildConfig + + private val omnibarFeatureFlag = FakeFeatureToggleFactory.create(ChangeOmnibarPositionFeature::class.java) + private val browserTheming = FakeFeatureToggleFactory.create(BrowserThemingFeature::class.java) + @SuppressLint("DenyListedApi") @Before fun before() { MockitoAnnotations.openMocks(this) @@ -76,15 +85,19 @@ internal class AppearanceViewModelTest { whenever(mockThemeSettingsDataStore.theme).thenReturn(DuckDuckGoTheme.SYSTEM_DEFAULT) whenever(mockAppSettingsDataStore.selectedFireAnimation).thenReturn(FireAnimation.HeroFire) whenever(mockAppSettingsDataStore.omnibarPosition).thenReturn(TOP) + whenever(mockAppBuildConfig.flavor).thenReturn(INTERNAL) - featureFlag.self().setRawStoredState(Toggle.State(enable = true)) + omnibarFeatureFlag.self().setRawStoredState(Toggle.State(enable = true)) + browserTheming.self().setRawStoredState(Toggle.State(enable = false)) testee = AppearanceViewModel( mockThemeSettingsDataStore, mockAppSettingsDataStore, mockPixel, coroutineTestRule.testDispatcherProvider, - featureFlag, + omnibarFeatureFlag, + mockAppBuildConfig, + browserTheming, ) } diff --git a/common/common-ui-experiments/.gitignore b/common/common-ui-experiments/.gitignore new file mode 100644 index 000000000000..42afabfd2abe --- /dev/null +++ b/common/common-ui-experiments/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/common-ui-experiments/build.gradle b/common/common-ui-experiments/build.gradle new file mode 100644 index 000000000000..7f1604bff1cc --- /dev/null +++ b/common/common-ui-experiments/build.gradle @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'com.squareup.anvil' +} + +apply from: "$rootProject.projectDir/gradle/android-library.gradle" + +android { + namespace 'com.duckduckgo.common.ui.experiments' +} + +android { + anvil { + generateDaggerFactories = true // default is false + } + lintOptions { + baseline file("lint-baseline.xml") + } +} + +dependencies { + + implementation project(path: ':common-utils') + implementation project(path: ':di') + anvil project(path: ':anvil-compiler') + implementation project(path: ':anvil-annotations') + implementation project(path: ':app-build-config-api') + + implementation AndroidX.appCompat + implementation Google.android.material + implementation AndroidX.constraintLayout + implementation AndroidX.core.splashscreen + implementation AndroidX.recyclerView + implementation AndroidX.lifecycle.viewModelKtx + // just to get the dagger annotations + implementation Google.dagger + + implementation "androidx.core:core-ktx:_" +} \ No newline at end of file diff --git a/common/common-ui-experiments/lint-baseline.xml b/common/common-ui-experiments/lint-baseline.xml new file mode 100644 index 000000000000..c584e1295716 --- /dev/null +++ b/common/common-ui-experiments/lint-baseline.xml @@ -0,0 +1,4 @@ + + + + diff --git a/common/common-ui-experiments/src/main/AndroidManifest.xml b/common/common-ui-experiments/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..005f6643616e --- /dev/null +++ b/common/common-ui-experiments/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/common/common-ui-experiments/src/main/java/com/duckduckgo/common/ui/experiments/BrowserThemingFeature.kt b/common/common-ui-experiments/src/main/java/com/duckduckgo/common/ui/experiments/BrowserThemingFeature.kt new file mode 100644 index 000000000000..379396f56ecf --- /dev/null +++ b/common/common-ui-experiments/src/main/java/com/duckduckgo/common/ui/experiments/BrowserThemingFeature.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.common.ui.experiments + +import com.duckduckgo.anvil.annotations.ContributesRemoteFeature +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.feature.toggles.api.Toggle + +@ContributesRemoteFeature( + scope = AppScope::class, + featureName = "experimentalBrowserTheming", +) +interface BrowserThemingFeature { + @Toggle.DefaultValue(false) + fun self(): Toggle +} diff --git a/common/common-ui/build.gradle b/common/common-ui/build.gradle index bf36030e672c..5942688b3327 100644 --- a/common/common-ui/build.gradle +++ b/common/common-ui/build.gradle @@ -38,6 +38,7 @@ android { dependencies { implementation project(path: ':common-utils') + implementation project(path: ':common-ui-experiments') implementation project(path: ':di') anvil project(path: ':anvil-compiler') implementation project(path: ':anvil-annotations') diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/DuckDuckGoActivity.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/DuckDuckGoActivity.kt index 1b695d0d660c..2b969c61e9f2 100644 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/DuckDuckGoActivity.kt +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/DuckDuckGoActivity.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK +import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK_EXPERIMENT import com.duckduckgo.common.ui.store.ThemingDataStore import com.duckduckgo.mobile.android.R import dagger.android.AndroidInjection @@ -97,6 +98,7 @@ abstract class DuckDuckGoActivity : DaggerActivity() { } } DARK -> true + DARK_EXPERIMENT -> true else -> false } } diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/Theming.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/Theming.kt index c631ad7be740..cfbe2f60a0a0 100644 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/Theming.kt +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/Theming.kt @@ -21,12 +21,11 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.res.Configuration -import android.graphics.drawable.Drawable -import android.view.ContextThemeWrapper import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.res.ResourcesCompat import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK +import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK_EXPERIMENT +import com.duckduckgo.common.ui.DuckDuckGoTheme.LIGHT_EXPERIMENT import com.duckduckgo.common.ui.DuckDuckGoTheme.SYSTEM_DEFAULT import com.duckduckgo.common.ui.Theming.Constants.BROADCAST_THEME_CHANGED import com.duckduckgo.common.ui.Theming.Constants.FIXED_THEME_ACTIVITIES @@ -36,6 +35,8 @@ enum class DuckDuckGoTheme { SYSTEM_DEFAULT, DARK, LIGHT, + DARK_EXPERIMENT, + LIGHT_EXPERIMENT, ; fun getOptionIndex(): Int { @@ -43,29 +44,13 @@ enum class DuckDuckGoTheme { SYSTEM_DEFAULT -> 1 LIGHT -> 2 DARK -> 3 + LIGHT_EXPERIMENT -> 4 + DARK_EXPERIMENT -> 5 } } } object Theming { - - fun getThemedDrawable( - context: Context, - drawableId: Int, - theme: DuckDuckGoTheme, - ): Drawable? { - val themeId = when (theme) { - SYSTEM_DEFAULT -> context.getSystemDefaultTheme() - DARK -> R.style.Theme_DuckDuckGo_Dark - else -> R.style.Theme_DuckDuckGo_Light - } - return ResourcesCompat.getDrawable( - context.resources, - drawableId, - ContextThemeWrapper(context, themeId).theme, - ) - } - object Constants { const val BROADCAST_THEME_CHANGED = "BROADCAST_THEME_CHANGED" val FIXED_THEME_ACTIVITIES = listOf( @@ -89,6 +74,8 @@ fun AppCompatActivity.getThemeId(theme: DuckDuckGoTheme): Int { return when (theme) { SYSTEM_DEFAULT -> getSystemDefaultTheme() DARK -> R.style.Theme_DuckDuckGo_Dark + LIGHT_EXPERIMENT -> R.style.Theme_DuckDuckGo_Light_Experiment + DARK_EXPERIMENT -> R.style.Theme_DuckDuckGo_Dark_Experiment else -> R.style.Theme_DuckDuckGo_Light } } diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/store/AppTheme.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/store/AppTheme.kt index 35eb67e85692..9d64543ab1c9 100644 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/store/AppTheme.kt +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/store/AppTheme.kt @@ -38,7 +38,9 @@ class BrowserAppTheme @Inject constructor( override fun isLightModeEnabled(): Boolean { return when (themeDataStore.theme) { DuckDuckGoTheme.LIGHT -> true + DuckDuckGoTheme.LIGHT_EXPERIMENT -> true DuckDuckGoTheme.DARK -> false + DuckDuckGoTheme.DARK_EXPERIMENT -> false DuckDuckGoTheme.SYSTEM_DEFAULT -> { !isNightMode(context) } diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/store/ThemingSharedPreferences.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/store/ThemingSharedPreferences.kt index 9783bd7923ca..f78775cad074 100644 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/store/ThemingSharedPreferences.kt +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/store/ThemingSharedPreferences.kt @@ -20,12 +20,13 @@ import android.content.Context import android.content.SharedPreferences import androidx.core.content.edit import com.duckduckgo.common.ui.DuckDuckGoTheme +import com.duckduckgo.common.ui.experiments.BrowserThemingFeature import javax.inject.Inject class ThemingSharedPreferences @Inject constructor( private val context: Context, -) : - ThemingDataStore { + private val browserThemingFeature: BrowserThemingFeature, +) : ThemingDataStore { private val themePrefMapper = ThemePrefsMapper() @@ -39,7 +40,7 @@ class ThemingSharedPreferences @Inject constructor( private fun selectedThemeSavedValue(): DuckDuckGoTheme { val savedValue = preferences.getString(KEY_THEME, null) - return themePrefMapper.themeFrom(savedValue, DuckDuckGoTheme.SYSTEM_DEFAULT) + return themePrefMapper.themeFrom(savedValue, DuckDuckGoTheme.SYSTEM_DEFAULT, browserThemingFeature.self().isEnabled()) } private val preferences: SharedPreferences by lazy { context.getSharedPreferences(FILENAME, Context.MODE_PRIVATE) } @@ -56,16 +57,27 @@ class ThemingSharedPreferences @Inject constructor( when (theme) { DuckDuckGoTheme.SYSTEM_DEFAULT -> THEME_SYSTEM_DEFAULT DuckDuckGoTheme.LIGHT -> THEME_LIGHT - DuckDuckGoTheme.DARK -> THEME_DARK + else -> THEME_DARK } fun themeFrom( value: String?, defValue: DuckDuckGoTheme, + isExperimentEnabled: Boolean, ) = when (value) { - THEME_LIGHT -> DuckDuckGoTheme.LIGHT - THEME_DARK -> DuckDuckGoTheme.DARK + THEME_LIGHT -> if (isExperimentEnabled) { + DuckDuckGoTheme.LIGHT_EXPERIMENT + } else { + DuckDuckGoTheme.LIGHT + } + + THEME_DARK -> if (isExperimentEnabled) { + DuckDuckGoTheme.DARK_EXPERIMENT + } else { + DuckDuckGoTheme.DARK + } + THEME_SYSTEM_DEFAULT -> DuckDuckGoTheme.SYSTEM_DEFAULT else -> defValue } diff --git a/common/common-ui/src/main/res/values/design-experiments-theming.xml b/common/common-ui/src/main/res/values/design-experiments-theming.xml new file mode 100644 index 000000000000..567f6589e321 --- /dev/null +++ b/common/common-ui/src/main/res/values/design-experiments-theming.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/common/common-ui/src/main/res/values/design-system-colors.xml b/common/common-ui/src/main/res/values/design-system-colors.xml index e912fe80ddc8..293bb80f4434 100644 --- a/common/common-ui/src/main/res/values/design-system-colors.xml +++ b/common/common-ui/src/main/res/values/design-system-colors.xml @@ -190,6 +190,9 @@ #FFFFFF #F2F5F9 + #9232E2 + #A060D9 + #A64FD1 #D4A1EE #6FBF5C