Skip to content

Commit 00d078e

Browse files
committed
Add new omnibar type selection UI
1 parent 451af5e commit 00d078e

File tree

5 files changed

+279
-8
lines changed

5 files changed

+279
-8
lines changed

app/src/main/java/com/duckduckgo/app/appearance/AppearanceActivity.kt

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,28 @@ import com.duckduckgo.common.ui.DuckDuckGoTheme.DARK
4343
import com.duckduckgo.common.ui.DuckDuckGoTheme.LIGHT
4444
import com.duckduckgo.common.ui.DuckDuckGoTheme.SYSTEM_DEFAULT
4545
import com.duckduckgo.common.ui.sendThemeChangedBroadcast
46+
import com.duckduckgo.common.ui.store.AppTheme
4647
import com.duckduckgo.common.ui.view.dialog.RadioListAlertDialogBuilder
4748
import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder
4849
import com.duckduckgo.common.ui.view.getColorFromAttr
50+
import com.duckduckgo.common.ui.view.gone
51+
import com.duckduckgo.common.ui.view.show
4952
import com.duckduckgo.common.ui.viewbinding.viewBinding
5053
import com.duckduckgo.di.scopes.ActivityScope
5154
import com.duckduckgo.navigation.api.getActivityParams
5255
import kotlinx.coroutines.flow.launchIn
5356
import kotlinx.coroutines.flow.onEach
5457
import logcat.logcat
58+
import javax.inject.Inject
59+
import com.duckduckgo.mobile.android.R as CommonR
5560

5661
@InjectWith(ActivityScope::class)
5762
@ContributeToActivityStarter(Default::class, screenName = "appearance")
5863
@ContributeToActivityStarter(HighlightedItem::class, screenName = "appearance")
5964
class AppearanceActivity : DuckDuckGoActivity() {
65+
@Inject
66+
lateinit var appTheme: AppTheme
67+
6068
private val viewModel: AppearanceViewModel by bindViewModel()
6169
private val binding: ActivityAppearanceBinding by viewBinding()
6270

@@ -110,10 +118,43 @@ class AppearanceActivity : DuckDuckGoActivity() {
110118
scrollToHighlightedItem()
111119
}
112120

121+
private fun configureOmnibarSettings(viewState: AppearanceViewModel.ViewState) {
122+
if (viewState.shouldShowSplitOmnibarSettings) {
123+
configureOmnibarTypeToggle(
124+
top = InputScreenToggleButton.Top(
125+
isActive = viewState.omnibarType == OmnibarType.SINGLE_TOP,
126+
isLightMode = appTheme.isLightModeEnabled(),
127+
),
128+
bottom = InputScreenToggleButton.Bottom(
129+
isActive = viewState.omnibarType == OmnibarType.SINGLE_BOTTOM,
130+
isLightMode = appTheme.isLightModeEnabled(),
131+
),
132+
split = InputScreenToggleButton.Split(
133+
isActive = viewState.omnibarType == OmnibarType.SPLIT,
134+
isLightMode = appTheme.isLightModeEnabled(),
135+
),
136+
)
137+
138+
binding.omnibarTypeSettingsTitle.show()
139+
binding.omnibarTypeToggleContainer.show()
140+
binding.showFullUrlSettingDivider.show()
141+
binding.addressBarPositionSetting.gone()
142+
} else {
143+
updateSelectedOmnibarPosition(viewState.omnibarType)
144+
binding.omnibarTypeSettingsTitle.gone()
145+
binding.omnibarTypeToggleContainer.gone()
146+
binding.showFullUrlSettingDivider.gone()
147+
binding.addressBarPositionSetting.show()
148+
}
149+
}
150+
113151
private fun configureUiEventHandlers() {
114152
binding.selectedThemeSetting.setClickListener { viewModel.userRequestedToChangeTheme() }
115153
binding.changeAppIconSetting.setOnClickListener { viewModel.userRequestedToChangeIcon() }
116154
binding.addressBarPositionSetting.setOnClickListener { viewModel.userRequestedToChangeAddressBarPosition() }
155+
binding.topOmnibarContainer.setOnClickListener { viewModel.onOmnibarTypeSelected(OmnibarType.SINGLE_TOP) }
156+
binding.bottomOmnibarContainer.setOnClickListener { viewModel.onOmnibarTypeSelected(OmnibarType.SINGLE_BOTTOM) }
157+
binding.splitOmnibarContainer.setOnClickListener { viewModel.onOmnibarTypeSelected(OmnibarType.SPLIT) }
117158
}
118159

119160
private fun observeViewModel() {
@@ -127,12 +168,12 @@ class AppearanceActivity : DuckDuckGoActivity() {
127168
binding.experimentalNightMode.quietlySetIsChecked(viewState.forceDarkModeEnabled, forceDarkModeToggleListener)
128169
binding.experimentalNightMode.isEnabled = viewState.canForceDarkMode
129170
binding.experimentalNightMode.isVisible = viewState.supportsForceDarkMode
130-
updateSelectedOmnibarPosition(it.omnibarType)
131171
binding.showFullUrlSetting.quietlySetIsChecked(viewState.isFullUrlEnabled, showFullUrlToggleListener)
132172
binding.showTrackersCountInTabSwitcher.quietlySetIsChecked(
133173
viewState.isTrackersCountInTabSwitcherEnabled,
134174
showTrackersCountInTabSwitcher,
135175
)
176+
configureOmnibarSettings(it)
136177
}
137178
}.launchIn(lifecycleScope)
138179

@@ -161,7 +202,7 @@ class AppearanceActivity : DuckDuckGoActivity() {
161202
when (omnibarType) {
162203
OmnibarType.SINGLE_TOP -> R.string.settingsAddressBarPositionTop
163204
OmnibarType.SINGLE_BOTTOM -> R.string.settingsAddressBarPositionBottom
164-
OmnibarType.SPLIT -> TODO()
205+
OmnibarType.SPLIT -> throw IllegalStateException("Split omnibar type should not be shown in address bar position setting")
165206
},
166207
)
167208
binding.addressBarPositionSetting.setSecondaryText(subtitle)
@@ -224,7 +265,7 @@ class AppearanceActivity : DuckDuckGoActivity() {
224265
object : RadioListAlertDialogBuilder.EventListener() {
225266
override fun onPositiveButtonClicked(selectedItem: Int) {
226267
val newType = OmnibarType.entries[selectedItem - 1]
227-
viewModel.setOmnibarType(newType)
268+
viewModel.onOmnibarTypeSelected(newType)
228269
}
229270
},
230271
).show()
@@ -260,6 +301,74 @@ class AppearanceActivity : DuckDuckGoActivity() {
260301
colorAnimator.start()
261302
}
262303

304+
private fun configureOmnibarTypeToggle(
305+
top: InputScreenToggleButton,
306+
bottom: InputScreenToggleButton,
307+
split: InputScreenToggleButton,
308+
) = with(binding) {
309+
val context = this@AppearanceActivity
310+
topOmnibarToggleImage.setImageDrawable(ContextCompat.getDrawable(context, top.imageRes))
311+
topOmnibarToggleCheck.setImageDrawable(ContextCompat.getDrawable(context, top.checkRes))
312+
313+
bottomOmnibarToggleImage.setImageDrawable(ContextCompat.getDrawable(context, bottom.imageRes))
314+
bottomOmnibarToggleCheck.setImageDrawable(ContextCompat.getDrawable(context, bottom.checkRes))
315+
316+
splitOmnibarToggleImage.setImageDrawable(ContextCompat.getDrawable(context, split.imageRes))
317+
splitOmnibarToggleCheck.setImageDrawable(ContextCompat.getDrawable(context, split.checkRes))
318+
}
319+
320+
private sealed class InputScreenToggleButton(
321+
isActive: Boolean,
322+
) {
323+
abstract val imageRes: Int
324+
325+
val checkRes: Int =
326+
if (isActive) {
327+
CommonR.drawable.ic_check_accent_24
328+
} else {
329+
CommonR.drawable.ic_shape_circle_disabled_24
330+
}
331+
332+
class Top(
333+
isActive: Boolean,
334+
isLightMode: Boolean,
335+
) : InputScreenToggleButton(isActive) {
336+
override val imageRes: Int =
337+
when {
338+
isActive && isLightMode -> R.drawable.mobile_toolbar_top_selected_light
339+
isActive && !isLightMode -> R.drawable.mobile_toolbar_top_selected_dark
340+
!isActive && isLightMode -> R.drawable.mobile_toolbar_top_unselected_light
341+
else -> R.drawable.mobile_toolbar_top_unselected_dark
342+
}
343+
}
344+
345+
class Bottom(
346+
isActive: Boolean,
347+
isLightMode: Boolean,
348+
) : InputScreenToggleButton(isActive) {
349+
override val imageRes: Int =
350+
when {
351+
isActive && isLightMode -> R.drawable.mobile_toolbar_bottom_selected_light
352+
isActive && !isLightMode -> R.drawable.mobile_toolbar_bottom_selected_dark
353+
!isActive && isLightMode -> R.drawable.mobile_toolbar_bottom_unselected_light
354+
else -> R.drawable.mobile_toolbar_bottom_unselected_dark
355+
}
356+
}
357+
358+
class Split(
359+
isActive: Boolean,
360+
isLightMode: Boolean,
361+
) : InputScreenToggleButton(isActive) {
362+
override val imageRes: Int =
363+
when {
364+
isActive && isLightMode -> R.drawable.mobile_toolbar_split_selected_light
365+
isActive && !isLightMode -> R.drawable.mobile_toolbar_split_selected_dark
366+
!isActive && isLightMode -> R.drawable.mobile_toolbar_split_unselected_light
367+
else -> R.drawable.mobile_toolbar_split_unselected_dark
368+
}
369+
}
370+
}
371+
263372
companion object {
264373
private const val ADDRESS_BAR = "addressBar"
265374
private const val FADE_DURATION = 300L

app/src/main/java/com/duckduckgo/app/appearance/AppearanceViewModel.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.duckduckgo.app.pixels.AppPixelName
2525
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_THEME_TOGGLED_DARK
2626
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_THEME_TOGGLED_LIGHT
2727
import com.duckduckgo.app.pixels.AppPixelName.SETTINGS_THEME_TOGGLED_SYSTEM_DEFAULT
28+
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
2829
import com.duckduckgo.app.settings.db.SettingsDataStore
2930
import com.duckduckgo.app.statistics.pixels.Pixel
3031
import com.duckduckgo.app.tabs.store.TabSwitcherDataStore
@@ -55,6 +56,7 @@ class AppearanceViewModel @Inject constructor(
5556
private val pixel: Pixel,
5657
private val dispatcherProvider: DispatcherProvider,
5758
private val tabSwitcherDataStore: TabSwitcherDataStore,
59+
private val browserFeatureFlags: AndroidBrowserConfigFeature,
5860
) : ViewModel() {
5961
data class ViewState(
6062
val theme: DuckDuckGoTheme = DuckDuckGoTheme.LIGHT,
@@ -65,6 +67,7 @@ class AppearanceViewModel @Inject constructor(
6567
val omnibarType: OmnibarType = OmnibarType.SINGLE_TOP,
6668
val isFullUrlEnabled: Boolean = true,
6769
val isTrackersCountInTabSwitcherEnabled: Boolean = true,
70+
val shouldShowSplitOmnibarSettings: Boolean = true,
6871
)
6972

7073
sealed class Command {
@@ -90,6 +93,8 @@ class AppearanceViewModel @Inject constructor(
9093
supportsForceDarkMode = WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING),
9194
isFullUrlEnabled = settingsDataStore.isFullUrlEnabled,
9295
omnibarType = settingsDataStore.omnibarType,
96+
shouldShowSplitOmnibarSettings = browserFeatureFlags.useUnifiedOmnibarLayout().isEnabled() &&
97+
browserFeatureFlags.splitOmnibar().isEnabled(),
9398
),
9499
)
95100

@@ -145,7 +150,7 @@ class AppearanceViewModel @Inject constructor(
145150
pixel.fire(pixelName)
146151
}
147152

148-
fun setOmnibarType(type: OmnibarType) {
153+
fun onOmnibarTypeSelected(type: OmnibarType) {
149154
viewModelScope.launch(dispatcherProvider.io()) {
150155
settingsDataStore.omnibarType = type
151156
viewState.update { it.copy(omnibarType = type) }

app/src/main/java/com/duckduckgo/app/pixels/remoteconfig/AndroidBrowserConfigFeature.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ interface AndroidBrowserConfigFeature {
193193
@Toggle.DefaultValue(TRUE)
194194
fun useUnifiedOmnibarLayout(): Toggle
195195

196-
@Toggle.DefaultValue(FALSE)
197-
@Toggle.InternalAlwaysEnabled
196+
@Toggle.DefaultValue(TRUE)
198197
fun splitOmnibar(): Toggle
199198
}

app/src/main/res/layout/activity_appearance.xml

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,164 @@
102102
app:primaryTextTruncated="false"
103103
app:secondaryText="@string/settingsAddressBarPositionTop" />
104104

105+
<com.duckduckgo.common.ui.view.listitem.SectionHeaderListItem
106+
android:id="@+id/omnibarTypeSettingsTitle"
107+
android:layout_width="match_parent"
108+
android:layout_height="wrap_content"
109+
app:primaryText="@string/settingsAddressBarPositionHeaderTitle" />
110+
111+
<androidx.constraintlayout.widget.ConstraintLayout
112+
android:id="@+id/omnibarTypeToggleContainer"
113+
android:layout_width="match_parent"
114+
android:layout_height="wrap_content"
115+
android:layout_marginTop="@dimen/keyline_3"
116+
android:layout_marginBottom="@dimen/keyline_4"
117+
android:layout_marginHorizontal="@dimen/keyline_4">
118+
119+
<LinearLayout
120+
android:id="@+id/topOmnibarContainer"
121+
android:layout_width="0dp"
122+
android:layout_height="wrap_content"
123+
android:gravity="center"
124+
android:orientation="vertical"
125+
app:layout_constraintWidth_max="160dp"
126+
app:layout_constraintBottom_toBottomOf="parent"
127+
app:layout_constraintEnd_toStartOf="@+id/bottomOmnibarContainer"
128+
app:layout_constraintHorizontal_chainStyle="packed"
129+
app:layout_constraintStart_toStartOf="parent"
130+
app:layout_constraintTop_toTopOf="parent">
131+
132+
<ImageView
133+
android:id="@+id/topOmnibarToggleImage"
134+
android:layout_width="match_parent"
135+
android:layout_height="wrap_content"
136+
android:scaleType="fitCenter"
137+
android:contentDescription="@string/settingsAddressBarPositionTop"
138+
android:src="@drawable/mobile_toolbar_top_selected_light" />
139+
140+
<com.duckduckgo.common.ui.view.text.DaxTextView
141+
android:layout_width="wrap_content"
142+
android:layout_height="wrap_content"
143+
android:layout_marginTop="6dp"
144+
android:gravity="center"
145+
android:text="@string/settingsAddressBarPositionTop"
146+
app:textType="primary"
147+
app:typography="caption" />
148+
149+
<ImageView
150+
android:id="@+id/topOmnibarToggleCheck"
151+
android:layout_width="wrap_content"
152+
android:layout_height="wrap_content"
153+
android:layout_marginTop="6dp"
154+
android:src="@drawable/ic_check_accent_24"
155+
android:contentDescription="@string/checkbox_label_title"/>
156+
157+
</LinearLayout>
158+
159+
<LinearLayout
160+
android:id="@+id/bottomOmnibarContainer"
161+
android:layout_width="0dp"
162+
android:layout_height="wrap_content"
163+
android:layout_marginStart="@dimen/inputScreenUserPrefToggleSpacing"
164+
android:gravity="center"
165+
android:orientation="vertical"
166+
app:layout_constraintWidth_max="160dp"
167+
app:layout_constraintBottom_toBottomOf="parent"
168+
app:layout_constraintEnd_toStartOf="@+id/splitOmnibarContainer"
169+
app:layout_constraintStart_toEndOf="@+id/topOmnibarContainer"
170+
app:layout_constraintTop_toTopOf="parent">
171+
172+
<ImageView
173+
android:id="@+id/bottomOmnibarToggleImage"
174+
android:layout_width="match_parent"
175+
android:layout_height="wrap_content"
176+
android:scaleType="fitCenter"
177+
android:contentDescription="@string/settingsAddressBarPositionBottom"
178+
android:src="@drawable/mobile_toolbar_bottom_unselected_light" />
179+
180+
<LinearLayout
181+
android:layout_width="wrap_content"
182+
android:layout_height="wrap_content"
183+
android:layout_marginTop="6dp"
184+
android:gravity="center"
185+
android:orientation="horizontal">
186+
187+
<com.duckduckgo.common.ui.view.text.DaxTextView
188+
android:layout_width="wrap_content"
189+
android:layout_height="wrap_content"
190+
android:gravity="center"
191+
android:text="@string/settingsAddressBarPositionBottom"
192+
app:textType="primary"
193+
app:typography="caption" />
194+
195+
</LinearLayout>
196+
197+
<ImageView
198+
android:id="@+id/bottomOmnibarToggleCheck"
199+
android:layout_width="wrap_content"
200+
android:layout_height="wrap_content"
201+
android:layout_marginTop="6dp"
202+
android:src="@drawable/ic_shape_circle_24"
203+
android:contentDescription="@string/checkbox_label_title"/>
204+
205+
</LinearLayout>
206+
207+
<LinearLayout
208+
android:id="@+id/splitOmnibarContainer"
209+
android:layout_width="0dp"
210+
android:layout_height="wrap_content"
211+
android:layout_marginStart="@dimen/inputScreenUserPrefToggleSpacing"
212+
android:gravity="center"
213+
android:orientation="vertical"
214+
app:layout_constraintWidth_max="160dp"
215+
app:layout_constraintBottom_toBottomOf="parent"
216+
app:layout_constraintEnd_toEndOf="parent"
217+
app:layout_constraintStart_toEndOf="@+id/bottomOmnibarContainer"
218+
app:layout_constraintTop_toTopOf="parent">
219+
220+
<ImageView
221+
android:id="@+id/splitOmnibarToggleImage"
222+
android:layout_width="match_parent"
223+
android:layout_height="wrap_content"
224+
android:scaleType="fitCenter"
225+
android:contentDescription="@string/settingsAddressBarPositionSplit"
226+
android:src="@drawable/mobile_toolbar_split_unselected_light" />
227+
228+
<LinearLayout
229+
android:layout_width="wrap_content"
230+
android:layout_height="wrap_content"
231+
android:layout_marginTop="6dp"
232+
android:gravity="center"
233+
android:orientation="horizontal">
234+
235+
<com.duckduckgo.common.ui.view.text.DaxTextView
236+
android:layout_width="wrap_content"
237+
android:layout_height="wrap_content"
238+
android:gravity="center"
239+
android:text="@string/settingsAddressBarPositionSplit"
240+
app:textType="primary"
241+
app:typography="caption" />
242+
243+
</LinearLayout>
244+
245+
<ImageView
246+
android:id="@+id/splitOmnibarToggleCheck"
247+
android:layout_width="wrap_content"
248+
android:layout_height="wrap_content"
249+
android:layout_marginTop="6dp"
250+
android:src="@drawable/ic_shape_circle_24"
251+
android:contentDescription="@string/checkbox_label_title"/>
252+
253+
</LinearLayout>
254+
255+
</androidx.constraintlayout.widget.ConstraintLayout>
256+
257+
<com.duckduckgo.common.ui.view.divider.HorizontalDivider
258+
android:id="@+id/showFullUrlSettingDivider"
259+
android:layout_width="match_parent"
260+
android:layout_height="wrap_content"
261+
android:paddingBottom="0dp" />
262+
105263
<com.duckduckgo.common.ui.view.listitem.OneLineListItem
106264
android:id="@+id/showFullUrlSetting"
107265
android:layout_width="match_parent"

0 commit comments

Comments
 (0)