diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
index 0e56be079caf..24605a09f5a2 100644
--- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
+++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
@@ -246,7 +246,6 @@ import com.duckduckgo.browser.api.ui.BrowserScreens.PrivateSearchScreenNoParams
import com.duckduckgo.browser.api.ui.BrowserScreens.WebViewActivityWithParams
import com.duckduckgo.common.ui.DuckDuckGoActivity
import com.duckduckgo.common.ui.DuckDuckGoFragment
-import com.duckduckgo.common.ui.anim.AnimationResourceProvider
import com.duckduckgo.common.ui.experiments.visual.store.ExperimentalThemingDataStore
import com.duckduckgo.common.ui.store.BrowserAppTheme
import com.duckduckgo.common.ui.tabs.SwipingTabsFeatureProvider
@@ -288,6 +287,7 @@ import com.duckduckgo.downloads.api.DownloadsFileActions
import com.duckduckgo.downloads.api.FileDownloader
import com.duckduckgo.downloads.api.FileDownloader.PendingFileDownload
import com.duckduckgo.duckchat.api.DuckChat
+import com.duckduckgo.duckchat.api.inputscreen.BrowserAndInputScreenTransitionProvider
import com.duckduckgo.duckchat.impl.inputscreen.ui.InputScreenActivity.Companion.QUERY
import com.duckduckgo.duckchat.impl.inputscreen.ui.InputScreenActivity.Companion.TAB_ID
import com.duckduckgo.duckchat.impl.inputscreen.ui.InputScreenActivityParams
@@ -574,6 +574,9 @@ class BrowserTabFragment :
@Inject
lateinit var omnibarTypeResolver: OmnibarTypeResolver
+ @Inject
+ lateinit var browserAndInputScreenTransitionProvider: BrowserAndInputScreenTransitionProvider
+
/**
* We use this to monitor whether the user was seeing the in-context Email Protection signup prompt
* This is needed because the activity stack will be cleared if an external link is opened in our browser
@@ -1075,8 +1078,8 @@ class BrowserTabFragment :
requireContext(),
InputScreenActivityParams(query = query),
)
- val enterTransition = AnimationResourceProvider.getSlideInFromTopFadeIn()
- val exitTransition = AnimationResourceProvider.getSlideOutToBottomFadeOut()
+ val enterTransition = browserAndInputScreenTransitionProvider.getInputScreenEnterAnimation()
+ val exitTransition = browserAndInputScreenTransitionProvider.getBrowserExitAnimation()
val options = ActivityOptionsCompat.makeCustomAnimation(
requireActivity(),
enterTransition,
diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/anim/AnimationResourceProvider.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/anim/AnimationResourceProvider.kt
deleted file mode 100644
index 2bb38527b83b..000000000000
--- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/anim/AnimationResourceProvider.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.anim
-
-import android.os.Build.VERSION
-import com.duckduckgo.mobile.android.R
-
-object AnimationResourceProvider {
-
- // The overshoot alpha transition can result in a black screen flash on older Android devices,
- // so we use the standard decelerate transition instead for these cases in the provider functions below.
- // The check is for version 34, as that's the range of devices on which we tested and confirmed that it works.
-
- fun getSlideInFromBottomFadeIn(): Int {
- return if (VERSION.SDK_INT >= 34) {
- R.anim.slide_in_from_bottom_fade_in_overshoot
- } else {
- R.anim.slide_in_from_bottom_fade_in
- }
- }
-
- fun getSlideOutToTopFadeOut(): Int {
- return if (VERSION.SDK_INT >= 34) {
- R.anim.slide_out_to_top_fade_out_overshoot
- } else {
- R.anim.slide_out_to_top_fade_out
- }
- }
-
- fun getSlideInFromTopFadeIn(): Int {
- return if (VERSION.SDK_INT >= 34) {
- R.anim.slide_in_from_top_fade_in_overshoot
- } else {
- R.anim.slide_in_from_top_fade_in
- }
- }
-
- fun getSlideOutToBottomFadeOut(): Int {
- return if (VERSION.SDK_INT >= 34) {
- R.anim.slide_out_to_bottom_fade_out_overshoot
- } else {
- R.anim.slide_out_to_bottom_fade_out
- }
- }
-}
diff --git a/duckchat/duckchat-api/src/main/java/com/duckduckgo/duckchat/api/inputscreen/BrowserAndInputScreenTransitionProvider.kt b/duckchat/duckchat-api/src/main/java/com/duckduckgo/duckchat/api/inputscreen/BrowserAndInputScreenTransitionProvider.kt
new file mode 100644
index 000000000000..c691d7f234bd
--- /dev/null
+++ b/duckchat/duckchat-api/src/main/java/com/duckduckgo/duckchat/api/inputscreen/BrowserAndInputScreenTransitionProvider.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.duckchat.api.inputscreen
+
+/**
+ * Provides animation resources for activity transitions between browser and input screen.
+ */
+interface BrowserAndInputScreenTransitionProvider {
+
+ fun getBrowserEnterAnimation(): Int
+
+ fun getBrowserExitAnimation(): Int
+
+ fun getInputScreenEnterAnimation(): Int
+
+ fun getInputScreenExitAnimation(): Int
+}
diff --git a/common/common-ui/src/main/res/anim/slide_in_from_bottom_fade_in_overshoot.xml b/duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_bottom_fade_in_dark.xml
similarity index 71%
rename from common/common-ui/src/main/res/anim/slide_in_from_bottom_fade_in_overshoot.xml
rename to duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_bottom_fade_in_dark.xml
index 35a9fc0ee607..2b839ad3edc9 100644
--- a/common/common-ui/src/main/res/anim/slide_in_from_bottom_fade_in_overshoot.xml
+++ b/duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_bottom_fade_in_dark.xml
@@ -1,5 +1,4 @@
-
-
+ android:backdropColor="@color/background_background_dark"
+ android:duration="@integer/slide_animation_duration_ms"
+ android:showBackdrop="true">
\ No newline at end of file
diff --git a/common/common-ui/src/main/res/anim/slide_in_from_top_fade_in_overshoot.xml b/duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_bottom_fade_in_light.xml
similarity index 68%
rename from common/common-ui/src/main/res/anim/slide_in_from_top_fade_in_overshoot.xml
rename to duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_bottom_fade_in_light.xml
index e0a7e572665f..d6a8fde9e84e 100644
--- a/common/common-ui/src/main/res/anim/slide_in_from_top_fade_in_overshoot.xml
+++ b/duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_bottom_fade_in_light.xml
@@ -1,5 +1,4 @@
-
-
+ android:backdropColor="@color/background_background_light"
+ android:duration="@integer/slide_animation_duration_ms"
+ android:showBackdrop="true">
\ No newline at end of file
diff --git a/common/common-ui/src/main/res/anim/slide_in_from_top_fade_in.xml b/duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_top_fade_in.xml
similarity index 77%
rename from common/common-ui/src/main/res/anim/slide_in_from_top_fade_in.xml
rename to duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_top_fade_in.xml
index 3a8753d8433b..f1ab7ef18e76 100644
--- a/common/common-ui/src/main/res/anim/slide_in_from_top_fade_in.xml
+++ b/duckchat/duckchat-api/src/main/res/anim-v33/slide_in_from_top_fade_in.xml
@@ -1,5 +1,4 @@
-
-
+ android:duration="@integer/slide_animation_duration_ms">
\ No newline at end of file
diff --git a/common/common-ui/src/main/res/anim/slide_out_to_bottom_fade_out_overshoot.xml b/duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_bottom_fade_out_dark.xml
similarity index 71%
rename from common/common-ui/src/main/res/anim/slide_out_to_bottom_fade_out_overshoot.xml
rename to duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_bottom_fade_out_dark.xml
index bb39e23bc50d..b9f4a81e70d9 100644
--- a/common/common-ui/src/main/res/anim/slide_out_to_bottom_fade_out_overshoot.xml
+++ b/duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_bottom_fade_out_dark.xml
@@ -1,5 +1,4 @@
-
-
+ android:backdropColor="@color/background_background_dark"
+ android:duration="@integer/slide_animation_duration_ms"
+ android:showBackdrop="true">
\ No newline at end of file
diff --git a/common/common-ui/src/main/res/anim/slide_out_to_top_fade_out_overshoot.xml b/duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_bottom_fade_out_light.xml
similarity index 68%
rename from common/common-ui/src/main/res/anim/slide_out_to_top_fade_out_overshoot.xml
rename to duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_bottom_fade_out_light.xml
index c13097b1311c..f9850f491ade 100644
--- a/common/common-ui/src/main/res/anim/slide_out_to_top_fade_out_overshoot.xml
+++ b/duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_bottom_fade_out_light.xml
@@ -1,5 +1,4 @@
-
-
+ android:backdropColor="@color/background_background_light"
+ android:duration="@integer/slide_animation_duration_ms"
+ android:showBackdrop="true">
+ android:interpolator="@anim/overshoot_interpolator_tension_1"
+ android:toYDelta="56dp" />
\ No newline at end of file
diff --git a/common/common-ui/src/main/res/anim/slide_out_to_top_fade_out.xml b/duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_top_fade_out.xml
similarity index 77%
rename from common/common-ui/src/main/res/anim/slide_out_to_top_fade_out.xml
rename to duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_top_fade_out.xml
index 476006e91d91..a15fd26a46dc 100644
--- a/common/common-ui/src/main/res/anim/slide_out_to_top_fade_out.xml
+++ b/duckchat/duckchat-api/src/main/res/anim-v33/slide_out_to_top_fade_out.xml
@@ -1,5 +1,4 @@
-
-
+ android:duration="@integer/slide_animation_duration_ms">
\ No newline at end of file
diff --git a/common/common-ui/src/main/res/anim/slide_in_from_bottom_fade_in.xml b/duckchat/duckchat-api/src/main/res/anim/fade_in.xml
similarity index 77%
rename from common/common-ui/src/main/res/anim/slide_in_from_bottom_fade_in.xml
rename to duckchat/duckchat-api/src/main/res/anim/fade_in.xml
index 42e5984a76cb..90b8fdf2c24e 100644
--- a/common/common-ui/src/main/res/anim/slide_in_from_bottom_fade_in.xml
+++ b/duckchat/duckchat-api/src/main/res/anim/fade_in.xml
@@ -1,5 +1,4 @@
-
-
-
-
+ android:duration="@integer/slide_animation_duration_ms">
\ No newline at end of file
diff --git a/common/common-ui/src/main/res/anim/slide_out_to_bottom_fade_out.xml b/duckchat/duckchat-api/src/main/res/anim/fade_out.xml
similarity index 77%
rename from common/common-ui/src/main/res/anim/slide_out_to_bottom_fade_out.xml
rename to duckchat/duckchat-api/src/main/res/anim/fade_out.xml
index 49d223c84b89..82629cdc47b7 100644
--- a/common/common-ui/src/main/res/anim/slide_out_to_bottom_fade_out.xml
+++ b/duckchat/duckchat-api/src/main/res/anim/fade_out.xml
@@ -1,5 +1,4 @@
-
-
-
-
+ android:duration="@integer/slide_animation_duration_ms">
\ No newline at end of file
diff --git a/common/common-ui/src/main/res/anim/overshoot_interpolator_tension_1.xml b/duckchat/duckchat-api/src/main/res/anim/overshoot_interpolator_tension_1.xml
similarity index 88%
rename from common/common-ui/src/main/res/anim/overshoot_interpolator_tension_1.xml
rename to duckchat/duckchat-api/src/main/res/anim/overshoot_interpolator_tension_1.xml
index 6f8515bc8193..211b9ba48077 100644
--- a/common/common-ui/src/main/res/anim/overshoot_interpolator_tension_1.xml
+++ b/duckchat/duckchat-api/src/main/res/anim/overshoot_interpolator_tension_1.xml
@@ -14,6 +14,5 @@
~ limitations under the License.
-->
-
\ No newline at end of file
diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/BrowserAndInputScreenTransitionProviderImpl.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/BrowserAndInputScreenTransitionProviderImpl.kt
new file mode 100644
index 000000000000..255ff1bf0206
--- /dev/null
+++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/BrowserAndInputScreenTransitionProviderImpl.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.duckchat.impl.inputscreen.ui
+
+import android.os.Build.VERSION
+import com.duckduckgo.common.ui.store.AppTheme
+import com.duckduckgo.di.scopes.AppScope
+import com.duckduckgo.duckchat.api.R
+import com.duckduckgo.duckchat.api.inputscreen.BrowserAndInputScreenTransitionProvider
+import com.squareup.anvil.annotations.ContributesBinding
+import javax.inject.Inject
+
+/**
+ * Provides animation resources for activity transitions between browser and input screen.
+ *
+ * ### API Level Behavior
+ * - **API < 33**: Uses simple fade animations as slide animations don't seem to be supported (on tested devices).
+ * - **API ≥ 33**: Uses slide animations with fade effects.
+ *
+ * ### Backdrop Color Handling
+ * Alpha transitions on activities fade the entire window, revealing the black system background.
+ * To prevent this visual artifact (especially noticeable in light mode), browser animations use
+ * a backdrop color that matches the activity background.
+ * Only one animation component needs backdrop color to prevent system background from showing through,
+ * so we're only applying it to browser animations.
+ *
+ * **Limitations:**
+ * - Backdrop color API is only available from API 33+, so black still shows through on lower APIs,
+ * but it's less dramatic because there's no slide animation.
+ * - Cannot use themeable attributes (`?attr`) as they cause crashes of the whole launcher (on tested devices).
+ * Instead, we use fixed color values and filter resources by current theme state.
+ */
+@ContributesBinding(scope = AppScope::class)
+class BrowserAndInputScreenTransitionProviderImpl @Inject constructor(
+ private val appTheme: AppTheme,
+) : BrowserAndInputScreenTransitionProvider {
+
+ override fun getBrowserEnterAnimation(): Int {
+ return if (VERSION.SDK_INT >= 33) {
+ if (appTheme.isLightModeEnabled()) {
+ R.anim.slide_in_from_bottom_fade_in_light
+ } else {
+ R.anim.slide_in_from_bottom_fade_in_dark
+ }
+ } else {
+ R.anim.fade_in
+ }
+ }
+
+ override fun getBrowserExitAnimation(): Int {
+ return if (VERSION.SDK_INT >= 33) {
+ if (appTheme.isLightModeEnabled()) {
+ R.anim.slide_out_to_bottom_fade_out_light
+ } else {
+ R.anim.slide_out_to_bottom_fade_out_dark
+ }
+ } else {
+ R.anim.fade_out
+ }
+ }
+
+ override fun getInputScreenEnterAnimation(): Int {
+ return if (VERSION.SDK_INT >= 33) {
+ R.anim.slide_in_from_top_fade_in
+ } else {
+ R.anim.fade_in
+ }
+ }
+
+ override fun getInputScreenExitAnimation(): Int {
+ return if (VERSION.SDK_INT >= 33) {
+ R.anim.slide_out_to_top_fade_out
+ } else {
+ R.anim.fade_out
+ }
+ }
+}
diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/InputScreenActivity.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/InputScreenActivity.kt
index 9898b001d4f8..fc75e97c2381 100644
--- a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/InputScreenActivity.kt
+++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/InputScreenActivity.kt
@@ -21,10 +21,11 @@ import android.os.Bundle
import com.duckduckgo.anvil.annotations.ContributeToActivityStarter
import com.duckduckgo.anvil.annotations.InjectWith
import com.duckduckgo.common.ui.DuckDuckGoActivity
-import com.duckduckgo.common.ui.anim.AnimationResourceProvider
import com.duckduckgo.di.scopes.ActivityScope
+import com.duckduckgo.duckchat.api.inputscreen.BrowserAndInputScreenTransitionProvider
import com.duckduckgo.duckchat.impl.R
import com.duckduckgo.navigation.api.GlobalActivityStarter
+import javax.inject.Inject
data class InputScreenActivityParams(
val query: String,
@@ -34,6 +35,9 @@ data class InputScreenActivityParams(
@ContributeToActivityStarter(InputScreenActivityParams::class)
class InputScreenActivity : DuckDuckGoActivity() {
+ @Inject
+ lateinit var browserAndInputScreenTransitionProvider: BrowserAndInputScreenTransitionProvider
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_input_screen)
@@ -45,8 +49,9 @@ class InputScreenActivity : DuckDuckGoActivity() {
}
private fun applyExitTransition() {
- val enterTransition = AnimationResourceProvider.getSlideInFromBottomFadeIn()
- val exitTransition = AnimationResourceProvider.getSlideOutToTopFadeOut()
+ val enterTransition = browserAndInputScreenTransitionProvider.getBrowserEnterAnimation()
+ val exitTransition = browserAndInputScreenTransitionProvider.getInputScreenExitAnimation()
+
if (VERSION.SDK_INT >= 34) {
overrideActivityTransition(
OVERRIDE_TRANSITION_CLOSE,