Skip to content

Commit aa85f2d

Browse files
committed
Add edge-to-edge to WebView, onboarding and misc activity groups
Roll out Android 15 edge-to-edge handling to three screen groups, each gated behind a new sub-toggle of the existing `edgeToEdge` remote feature (default INTERNAL) so the rollout is remotely controllable per group: - webview: WebView, SettingsWebView, Subscriptions/Pir dashboards, EmailProtection in-context signup, GPM/Takeout import flows, PrivacyDashboard, Survey - onboarding: SystemSearch, WidgetThemeConfiguration, AddWidgetInstructions - misc: About, ChangeIcon, Bookmarks, BookmarkFolders, Downloads, GetDesktopBrowser, Windows, MacOs, Feedback, SubscriptionFeedback, RestoreSubscription, ChangePlan, Pir, EmailProtectionUnsupported Each activity enables a transparent edge-to-edge window when its bucket is enabled and applies status-bar / navigation-bar / display-cutout / IME insets via EdgeToEdgeHandler. Adds a Theme.DuckDuckGo.SplashScreen.EdgeToEdge theme (with a values-land override that reserves the display cutout as a letterbox in landscape) for splash-themed screens, plus the common-utils MISC bucket, misc() toggle, provider wiring and unit tests.
1 parent 1cfb317 commit aa85f2d

56 files changed

Lines changed: 831 additions & 31 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="utf-8"?><!--
2+
~ Copyright (c) 2026 DuckDuckGo
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<resources>
18+
19+
<!--
20+
Landscape override: reserve the display cutout as a dark letterbox for edge-to-edge splash-themed
21+
screens. The base splash theme (AndroidX Theme.SplashScreen) forces "shortEdges", which persists
22+
over the runtime cutout handling, so we set "never" declaratively here where there is no timing race.
23+
-->
24+
<style name="Theme.DuckDuckGo.SplashScreen.EdgeToEdge" parent="Theme.DuckDuckGo.SplashScreen">
25+
<item name="android:windowLayoutInDisplayCutoutMode">never</item>
26+
</style>
27+
28+
</resources>

android-design-system/design-system/src/main/res/values/design-system-theming.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,15 @@
477477
<item name="postSplashScreenTheme">@style/Theme.DuckDuckGo.Light</item>
478478
</style>
479479

480+
<!--
481+
Splash-screen theme variant for edge-to-edge screens (e.g. SystemSearchActivity,
482+
WidgetThemeConfiguration). Portrait keeps the splash defaults; the values-land override sets
483+
layoutInDisplayCutoutMode to "never" so in landscape the cutout renders as a dark letterbox
484+
instead of the window background bleeding into it (the base splash theme forces "shortEdges",
485+
which otherwise overrides the runtime cutout handling).
486+
-->
487+
<style name="Theme.DuckDuckGo.SplashScreen.EdgeToEdge" parent="Theme.DuckDuckGo.SplashScreen" />
488+
480489
<style name="Theme.DuckDuckGo.Splash" parent="Theme.SplashScreen">
481490
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_dax_splash_screen_icon</item>
482491
</style>

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@
229229
android:exported="true"
230230
android:label="@string/appName"
231231
android:roundIcon="${appIconRound}"
232-
android:theme="@style/Theme.DuckDuckGo.SplashScreen"
232+
android:theme="@style/Theme.DuckDuckGo.SplashScreen.EdgeToEdge"
233233
android:launchMode="singleTask"
234234
android:stateNotNeeded="true">
235235

@@ -490,7 +490,7 @@
490490
android:exported="false"
491491
android:label="@string/widgetConfigurationActivityTitle"
492492
android:roundIcon="${appIconRound}"
493-
android:theme="@style/Theme.DuckDuckGo.SplashScreen">
493+
android:theme="@style/Theme.DuckDuckGo.SplashScreen.EdgeToEdge">
494494
<intent-filter>
495495
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
496496
</intent-filter>

app/src/main/java/com/duckduckgo/app/WidgetThemeConfiguration.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ import com.duckduckgo.app.pixels.AppPixelName
3030
import com.duckduckgo.app.statistics.pixels.Pixel
3131
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
3232
import com.duckduckgo.common.ui.DuckDuckGoActivity
33+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeBucket
34+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeHandler
35+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeProvider
3336
import com.duckduckgo.di.scopes.ActivityScope
3437
import com.duckduckgo.widget.SearchAndFavoritesWidget
3538
import com.duckduckgo.widget.WidgetPreferences
@@ -48,13 +51,29 @@ class WidgetThemeConfiguration : DuckDuckGoActivity() {
4851
@Inject
4952
lateinit var appBuildConfig: AppBuildConfig
5053

54+
@Inject
55+
lateinit var edgeToEdgeProvider: EdgeToEdgeProvider
56+
57+
@Inject
58+
lateinit var edgeToEdgeHandler: EdgeToEdgeHandler
59+
5160
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
5261

5362
@Suppress("NewApi") // we use appBuildConfig
5463
override fun onCreate(savedInstanceState: Bundle?) {
5564
super.onCreate(savedInstanceState)
5665
val binding = ActivityWidgetConfigurationBinding.inflate(layoutInflater)
66+
val edgeToEdgeEnabled = edgeToEdgeProvider.isEnabled(EdgeToEdgeBucket.ONBOARDING)
67+
if (edgeToEdgeEnabled) {
68+
enableTransparentEdgeToEdge()
69+
}
5770
setContentView(binding.root)
71+
if (edgeToEdgeEnabled) {
72+
// Panel is top-anchored under this opaque theme, so status + sides go on the root; the nav-bar
73+
// pad on the panel keeps the Add button clear if the panel is ever bottom-aligned.
74+
edgeToEdgeHandler.applyStatusBarAndHorizontalInsets(binding.root)
75+
edgeToEdgeHandler.applyNavigationBarInsets(binding.widgetConfigPanel, drawBehindGestureNav = false)
76+
}
5877
val extras = intent.extras
5978
extras?.let {
6079
appWidgetId = it.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)

app/src/main/java/com/duckduckgo/app/about/AboutDuckDuckGoActivity.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ import com.duckduckgo.common.ui.DuckDuckGoActivity
4949
import com.duckduckgo.common.ui.view.getColorFromAttr
5050
import com.duckduckgo.common.ui.viewbinding.viewBinding
5151
import com.duckduckgo.common.utils.AppUrl.Url
52+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeBucket
53+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeHandler
54+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeProvider
5255
import com.duckduckgo.di.scopes.ActivityScope
5356
import com.duckduckgo.mobile.android.R.attr
5457
import com.duckduckgo.navigation.api.GlobalActivityStarter
@@ -78,18 +81,39 @@ class AboutDuckDuckGoActivity : DuckDuckGoActivity() {
7881
@Inject
7982
lateinit var globalActivityStarter: GlobalActivityStarter
8083

84+
@Inject
85+
lateinit var edgeToEdgeProvider: EdgeToEdgeProvider
86+
87+
@Inject
88+
lateinit var edgeToEdgeHandler: EdgeToEdgeHandler
89+
8190
override fun onCreate(savedInstanceState: Bundle?) {
8291
super.onCreate(savedInstanceState)
8392

93+
val edgeToEdgeEnabled = edgeToEdgeProvider.isEnabled(EdgeToEdgeBucket.MISC)
94+
if (edgeToEdgeEnabled) {
95+
enableTransparentEdgeToEdge()
96+
}
97+
8498
setContentView(binding.root)
8599
setupToolbar(binding.includeToolbar.toolbar)
86100
supportActionBar?.setTitle(R.string.aboutActivityTitleNew)
87101

102+
if (edgeToEdgeEnabled) {
103+
configureEdgeToEdgeInsets()
104+
}
105+
88106
configureUiEventHandlers()
89107
observeViewModel()
90108
configureClickableLinks()
91109
}
92110

111+
private fun configureEdgeToEdgeInsets() {
112+
edgeToEdgeHandler.applyHorizontalSystemBarInsets(binding.root)
113+
edgeToEdgeHandler.applyStatusBarInsets(binding.includeToolbar.appBarLayout)
114+
edgeToEdgeHandler.applyNavigationBarInsets(binding.includeContent.longDescriptionContainer, drawBehindGestureNav = true)
115+
}
116+
93117
override fun onResume() {
94118
super.onResume()
95119
viewModel.resetEasterEggCounter()

app/src/main/java/com/duckduckgo/app/browser/webview/WebViewActivity.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ import com.duckduckgo.app.statistics.pixels.Pixel
3333
import com.duckduckgo.browser.api.ui.BrowserScreens.WebViewActivityWithParams
3434
import com.duckduckgo.common.ui.DuckDuckGoActivity
3535
import com.duckduckgo.common.ui.viewbinding.viewBinding
36+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeBucket
37+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeHandler
38+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeProvider
3639
import com.duckduckgo.di.scopes.ActivityScope
3740
import com.duckduckgo.navigation.api.getActivityParams
3841
import com.duckduckgo.user.agent.api.UserAgentProvider
@@ -51,6 +54,12 @@ class WebViewActivity : DuckDuckGoActivity() {
5154
@Inject
5255
lateinit var pixel: Pixel
5356

57+
@Inject
58+
lateinit var edgeToEdgeProvider: EdgeToEdgeProvider
59+
60+
@Inject
61+
lateinit var edgeToEdgeHandler: EdgeToEdgeHandler
62+
5463
private val binding: ActivityWebviewBinding by viewBinding()
5564

5665
private val toolbar
@@ -60,9 +69,18 @@ class WebViewActivity : DuckDuckGoActivity() {
6069
override fun onCreate(savedInstanceState: Bundle?) {
6170
super.onCreate(savedInstanceState)
6271

72+
val edgeToEdgeEnabled = edgeToEdgeProvider.isEnabled(EdgeToEdgeBucket.WEBVIEW)
73+
if (edgeToEdgeEnabled) {
74+
enableTransparentEdgeToEdge()
75+
}
76+
6377
setContentView(binding.root)
6478
setupToolbar(toolbar)
6579

80+
if (edgeToEdgeEnabled) {
81+
configureEdgeToEdgeInsets()
82+
}
83+
6684
val params = intent.getActivityParams(WebViewActivityWithParams::class.java)
6785
val url = params?.url
6886
title = params?.screenTitle.orEmpty()
@@ -110,6 +128,12 @@ class WebViewActivity : DuckDuckGoActivity() {
110128
}
111129
}
112130

131+
private fun configureEdgeToEdgeInsets() {
132+
edgeToEdgeHandler.applyHorizontalSystemBarInsets(binding.root)
133+
edgeToEdgeHandler.applyStatusBarInsets(binding.includeToolbar.appBarLayout)
134+
edgeToEdgeHandler.applyNavigationBarInsets(binding.webViewContainer, drawBehindGestureNav = true)
135+
}
136+
113137
override fun onOptionsItemSelected(item: MenuItem): Boolean {
114138
when (item.itemId) {
115139
android.R.id.home -> {

app/src/main/java/com/duckduckgo/app/desktopbrowser/GetDesktopBrowserActivity.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ import com.duckduckgo.app.browser.R
3333
import com.duckduckgo.app.browser.databinding.ActivityGetDesktopBrowserBinding
3434
import com.duckduckgo.common.ui.DuckDuckGoActivity
3535
import com.duckduckgo.common.ui.viewbinding.viewBinding
36+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeBucket
37+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeHandler
38+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeProvider
3639
import com.duckduckgo.di.scopes.ActivityScope
3740
import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams
3841
import com.duckduckgo.navigation.api.getActivityParams
@@ -50,6 +53,12 @@ class GetDesktopBrowserActivity : DuckDuckGoActivity() {
5053
@Inject
5154
lateinit var getDesktopBrowserViewModelFactory: GetDesktopBrowserViewModel.Factory
5255

56+
@Inject
57+
lateinit var edgeToEdgeProvider: EdgeToEdgeProvider
58+
59+
@Inject
60+
lateinit var edgeToEdgeHandler: EdgeToEdgeHandler
61+
5362
private val binding: ActivityGetDesktopBrowserBinding by viewBinding()
5463

5564
private val viewModel by lazy {
@@ -60,12 +69,28 @@ class GetDesktopBrowserActivity : DuckDuckGoActivity() {
6069

6170
override fun onCreate(savedInstanceState: Bundle?) {
6271
super.onCreate(savedInstanceState)
72+
val edgeToEdgeEnabled = edgeToEdgeProvider.isEnabled(EdgeToEdgeBucket.MISC)
73+
if (edgeToEdgeEnabled) {
74+
enableTransparentEdgeToEdge()
75+
}
6376
setContentView(binding.root)
6477

6578
setupObservers()
6679
setupBackNavigationHandler()
6780
setupClickListeners()
6881
setupToolbar(binding.includeToolbar.toolbar)
82+
83+
if (edgeToEdgeEnabled) {
84+
configureEdgeToEdgeInsets()
85+
}
86+
}
87+
88+
private fun configureEdgeToEdgeInsets() {
89+
edgeToEdgeHandler.applyHorizontalSystemBarInsets(binding.root)
90+
edgeToEdgeHandler.applyStatusBarInsets(binding.includeToolbar.appBarLayout)
91+
// Content ends in fixed bottom buttons (Share download link / No thanks), so keep them clear of the nav
92+
// bar in every mode (padded above the gesture-nav chin) rather than drawing behind the gesture handle.
93+
edgeToEdgeHandler.applyNavigationBarInsets(binding.contentScrollView, drawBehindGestureNav = false)
6994
}
7095

7196
private fun setupObservers() {

app/src/main/java/com/duckduckgo/app/downloads/DownloadsActivity.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ import com.duckduckgo.common.ui.view.hideKeyboard
4141
import com.duckduckgo.common.ui.view.show
4242
import com.duckduckgo.common.ui.view.showKeyboard
4343
import com.duckduckgo.common.ui.viewbinding.viewBinding
44+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeBucket
45+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeHandler
46+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeProvider
4447
import com.duckduckgo.di.scopes.ActivityScope
4548
import com.duckduckgo.downloads.api.DownloadsFileActions
4649
import com.duckduckgo.downloads.api.DownloadsScreens.DownloadsScreenNoParams
@@ -65,6 +68,12 @@ class DownloadsActivity : DuckDuckGoActivity() {
6568
@Inject
6669
lateinit var downloadsFileActions: DownloadsFileActions
6770

71+
@Inject
72+
lateinit var edgeToEdgeProvider: EdgeToEdgeProvider
73+
74+
@Inject
75+
lateinit var edgeToEdgeHandler: EdgeToEdgeHandler
76+
6877
private val toolbar
6978
get() = binding.toolbar
7079

@@ -75,9 +84,16 @@ class DownloadsActivity : DuckDuckGoActivity() {
7584

7685
override fun onCreate(savedInstanceState: Bundle?) {
7786
super.onCreate(savedInstanceState)
87+
val edgeToEdgeEnabled = edgeToEdgeProvider.isEnabled(EdgeToEdgeBucket.MISC)
88+
if (edgeToEdgeEnabled) {
89+
enableTransparentEdgeToEdge()
90+
}
7891
setContentView(binding.root)
7992
setupToolbar(toolbar)
8093
setupRecyclerView()
94+
if (edgeToEdgeEnabled) {
95+
configureEdgeToEdgeInsets()
96+
}
8197

8298
lifecycleScope.launch {
8399
viewModel.viewState
@@ -92,6 +108,12 @@ class DownloadsActivity : DuckDuckGoActivity() {
92108
}
93109
}
94110

111+
private fun configureEdgeToEdgeInsets() {
112+
edgeToEdgeHandler.applyHorizontalSystemBarInsets(binding.root)
113+
edgeToEdgeHandler.applyStatusBarInsets(binding.appBarLayout)
114+
edgeToEdgeHandler.applyNavigationBarInsets(binding.downloadsContentView, drawBehindGestureNav = true)
115+
}
116+
95117
override fun onResume() {
96118
super.onResume()
97119
viewModel.syncDownloads()

app/src/main/java/com/duckduckgo/app/email/ui/EmailProtectionUnsupportedActivity.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import com.duckduckgo.anvil.annotations.InjectWith
2222
import com.duckduckgo.app.browser.databinding.ActivityEmailUnsupportedBinding
2323
import com.duckduckgo.common.ui.DuckDuckGoActivity
2424
import com.duckduckgo.common.ui.viewbinding.viewBinding
25+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeBucket
26+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeHandler
27+
import com.duckduckgo.common.utils.edgetoedge.EdgeToEdgeProvider
2528
import com.duckduckgo.di.scopes.ActivityScope
29+
import javax.inject.Inject
2630

2731
@InjectWith(ActivityScope::class)
2832
@ContributeToActivityStarter(EmailProtectionUnsupportedScreenNoParams::class)
@@ -33,10 +37,31 @@ class EmailProtectionUnsupportedActivity : DuckDuckGoActivity() {
3337
private val toolbar
3438
get() = binding.includeToolbar.toolbar
3539

40+
@Inject
41+
lateinit var edgeToEdgeProvider: EdgeToEdgeProvider
42+
43+
@Inject
44+
lateinit var edgeToEdgeHandler: EdgeToEdgeHandler
45+
3646
override fun onCreate(savedInstanceState: Bundle?) {
3747
super.onCreate(savedInstanceState)
3848

49+
val edgeToEdgeEnabled = edgeToEdgeProvider.isEnabled(EdgeToEdgeBucket.MISC)
50+
if (edgeToEdgeEnabled) {
51+
enableTransparentEdgeToEdge()
52+
}
53+
3954
setContentView(binding.root)
4055
setupToolbar(toolbar)
56+
57+
if (edgeToEdgeEnabled) {
58+
configureEdgeToEdgeInsets()
59+
}
60+
}
61+
62+
private fun configureEdgeToEdgeInsets() {
63+
edgeToEdgeHandler.applyHorizontalSystemBarInsets(binding.root)
64+
edgeToEdgeHandler.applyStatusBarInsets(binding.includeToolbar.appBarLayout)
65+
edgeToEdgeHandler.applyNavigationBarInsets(binding.contentContainer, drawBehindGestureNav = true)
4166
}
4267
}

0 commit comments

Comments
 (0)