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