Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Omnibar position option #4885

Open
wants to merge 27 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bff74db
Fix a warning
0nko Aug 9, 2024
0e27843
Fix the build
0nko Aug 29, 2024
c895f39
Omnibar position: Settings and store (#4886)
0nko Aug 30, 2024
5757447
Omnibar position: Bottom app bar layout (#4930)
0nko Aug 30, 2024
ce75378
Add a delay to showing the menu so that the keyboard has time to disa…
0nko Sep 3, 2024
c69d358
Omnibar position: Menu (#4940)
0nko Sep 9, 2024
42ac3ab
Implementation: [Android] Show a special notice to users in Indonesia…
anikiki Sep 4, 2024
9d3f6e9
Restacking
0nko Sep 9, 2024
7758bbe
Omnibar position: Dummy toolbar (#4941)
0nko Sep 9, 2024
e4a996f
Omnibar position: Onboarding messages (#4942)
0nko Sep 9, 2024
3cd1225
Omnibar position: Autocomplete (#4945)
0nko Sep 9, 2024
d7e521e
Omnibar position: Snackbar (#4954)
0nko Sep 9, 2024
605afad
Fix ktlint issues
0nko Sep 9, 2024
0e1c3f8
Omnibar position: Privacy popup dialog (#4962)
0nko Sep 9, 2024
b2c4d54
Remove duplicate strings
0nko Sep 9, 2024
7a125fe
Fix the build after rebasing
0nko Sep 12, 2024
fe5e5a4
Revert the warning fix
0nko Sep 12, 2024
7e4da7b
Replace a number literal with a constant
0nko Sep 12, 2024
c0094eb
Replace a delay number literal with a constant
0nko Sep 12, 2024
1c383d4
Rename the popup menu method
0nko Sep 12, 2024
3fd5cab
Use a separate delay for the popup menu
0nko Sep 12, 2024
8c65bc9
Move the OmnibarPosition to the app module
0nko Sep 12, 2024
38d7d3d
Omnibar position: Pixels (#4974)
0nko Sep 19, 2024
dc3722b
Omnibar position: Translations (#4993)
0nko Sep 19, 2024
999649e
Omnibar position: UI fixes (#5003)
0nko Sep 19, 2024
691741d
Fix the tests
0nko Sep 19, 2024
d47f7d5
Fix the formatting
0nko Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ import com.duckduckgo.app.browser.model.LongPressTarget
import com.duckduckgo.app.browser.newtab.FavoritesQuickAccessAdapter
import com.duckduckgo.app.browser.newtab.FavoritesQuickAccessAdapter.QuickAccessFavorite
import com.duckduckgo.app.browser.omnibar.OmnibarEntryConverter
import com.duckduckgo.app.browser.omnibar.model.OmnibarPosition.BOTTOM
import com.duckduckgo.app.browser.omnibar.model.OmnibarPosition.TOP
import com.duckduckgo.app.browser.remotemessage.RemoteMessagingModel
import com.duckduckgo.app.browser.session.WebViewSessionStorage
import com.duckduckgo.app.browser.viewstate.BrowserViewState
Expand Down Expand Up @@ -197,7 +199,13 @@ import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.history.api.HistoryEntry.VisitedPage
import com.duckduckgo.history.api.NavigationHistory
import com.duckduckgo.newtabpage.impl.pixels.NewTabPixels
import com.duckduckgo.privacy.config.api.*
import com.duckduckgo.privacy.config.api.AmpLinkInfo
import com.duckduckgo.privacy.config.api.AmpLinks
import com.duckduckgo.privacy.config.api.ContentBlocking
import com.duckduckgo.privacy.config.api.GpcException
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.api.TrackingParameters
import com.duckduckgo.privacy.config.api.UnprotectedTemporary
import com.duckduckgo.privacy.config.impl.features.gpc.RealGpc
import com.duckduckgo.privacy.config.impl.features.gpc.RealGpc.Companion.GPC_HEADER
import com.duckduckgo.privacy.config.impl.features.gpc.RealGpc.Companion.GPC_HEADER_VALUE
Expand Down Expand Up @@ -247,7 +255,14 @@ import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.json.JSONObject
import org.junit.After
import org.junit.Assert.*
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand All @@ -258,11 +273,15 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.internal.util.DefaultMockingDetails
import org.mockito.kotlin.*
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@FlowPreview
Expand Down Expand Up @@ -493,6 +512,7 @@ class BrowserTabViewModelTest {
whenever(mockSavedSitesRepository.getBookmarks()).thenReturn(bookmarksListFlow.consumeAsFlow())
whenever(mockRemoteMessagingRepository.messageFlow()).thenReturn(remoteMessageFlow.consumeAsFlow())
whenever(mockSettingsDataStore.automaticFireproofSetting).thenReturn(AutomaticFireproofSetting.ASK_EVERY_TIME)
whenever(mockSettingsDataStore.omnibarPosition).thenReturn(TOP)
whenever(androidBrowserConfig.screenLock()).thenReturn(mockEnabledToggle)
whenever(mockSSLCertificatesFeature.allowBypass()).thenReturn(mockEnabledToggle)
whenever(mockExtendedOnboardingFeatureToggles.aestheticUpdates()).thenReturn(mockEnabledToggle)
Expand Down Expand Up @@ -5167,11 +5187,20 @@ class BrowserTabViewModelTest {
}

@Test
fun whenRefreshIsTriggeredByUserThenPrivacyProtectionsPopupManagerIsNotified() = runTest {
fun whenRefreshIsTriggeredByUserThenPrivacyProtectionsPopupManagerIsNotifiedWithTopPosition() = runTest {
testee.onRefreshRequested(triggeredByUser = false)
verify(mockPrivacyProtectionsPopupManager, never()).onPageRefreshTriggeredByUser()
verify(mockPrivacyProtectionsPopupManager, never()).onPageRefreshTriggeredByUser(isOmnibarAtTheTop = true)
testee.onRefreshRequested(triggeredByUser = true)
verify(mockPrivacyProtectionsPopupManager).onPageRefreshTriggeredByUser()
verify(mockPrivacyProtectionsPopupManager).onPageRefreshTriggeredByUser(isOmnibarAtTheTop = true)
}

@Test
fun whenRefreshIsTriggeredByUserThenPrivacyProtectionsPopupManagerIsNotifiedWithBottomPosition() = runTest {
whenever(mockSettingsDataStore.omnibarPosition).thenReturn(BOTTOM)
testee.onRefreshRequested(triggeredByUser = false)
verify(mockPrivacyProtectionsPopupManager, never()).onPageRefreshTriggeredByUser(isOmnibarAtTheTop = false)
testee.onRefreshRequested(triggeredByUser = true)
verify(mockPrivacyProtectionsPopupManager).onPageRefreshTriggeredByUser(isOmnibarAtTheTop = false)
}

@Test
Expand Down Expand Up @@ -5459,7 +5488,7 @@ class BrowserTabViewModelTest {

@Test
fun whenTrackersBlockedCtaShownThenPrivacyShieldIsHighlighted() = runTest {
val cta = OnboardingDaxDialogCta.DaxTrackersBlockedCta(mockOnboardingStore, mockAppInstallStore, emptyList())
val cta = OnboardingDaxDialogCta.DaxTrackersBlockedCta(mockOnboardingStore, mockAppInstallStore, emptyList(), mockSettingsDataStore)
testee.ctaViewState.value = ctaViewState().copy(cta = cta)

testee.onOnboardingDaxTypingAnimationFinished()
Expand All @@ -5469,7 +5498,7 @@ class BrowserTabViewModelTest {

@Test
fun givenPrivacyShieldHighlightedWhenShieldIconSelectedThenStopPulse() = runTest {
val cta = OnboardingDaxDialogCta.DaxTrackersBlockedCta(mockOnboardingStore, mockAppInstallStore, emptyList())
val cta = OnboardingDaxDialogCta.DaxTrackersBlockedCta(mockOnboardingStore, mockAppInstallStore, emptyList(), mockSettingsDataStore)
testee.ctaViewState.value = ctaViewState().copy(cta = cta)

testee.onPrivacyShieldSelected()
Expand All @@ -5488,7 +5517,7 @@ class BrowserTabViewModelTest {

@Test
fun whenUserDismissDaxTrackersBlockedDialogThenFinishPrivacyShieldPulse() {
val cta = OnboardingDaxDialogCta.DaxTrackersBlockedCta(mockOnboardingStore, mockAppInstallStore, emptyList())
val cta = OnboardingDaxDialogCta.DaxTrackersBlockedCta(mockOnboardingStore, mockAppInstallStore, emptyList(), mockSettingsDataStore)
setCta(cta)

testee.onUserDismissedCta(cta)
Expand Down Expand Up @@ -5788,6 +5817,50 @@ class BrowserTabViewModelTest {
assertTrue(browserViewState().showDuckPlayerIcon)
}

@Test
fun whenLoadingIsNotFinishedAndOmnibarIsAtTheTopDoNotCheckIfWebViewMustBePadded() = runTest {
whenever(mockSettingsDataStore.omnibarPosition).thenReturn(TOP)
val state = WebViewNavigationState(TestBackForwardList(), 50)

testee.browserViewState.value = browserViewState().copy(browserShowing = true)
testee.navigationStateChanged(state)

assertCommandNotIssued<Command.MakeOmnibarStickyIfNeeded>()
}

@Test
fun whenLoadingIsFinishedAndOmnibarIsAtTheTopDoNotCheckIfWebViewMustBePadded() = runTest {
whenever(mockSettingsDataStore.omnibarPosition).thenReturn(TOP)
val state = WebViewNavigationState(TestBackForwardList(), 100)

testee.browserViewState.value = browserViewState().copy(browserShowing = true)
testee.navigationStateChanged(state)

assertCommandNotIssued<Command.MakeOmnibarStickyIfNeeded>()
}

@Test
fun whenLoadingIsNotFinishedAndOmnibarIsAtTheBottomDoNotCheckIfWebViewMustBePadded() = runTest {
whenever(mockSettingsDataStore.omnibarPosition).thenReturn(BOTTOM)
val state = WebViewNavigationState(TestBackForwardList(), 50)

testee.browserViewState.value = browserViewState().copy(browserShowing = true)
testee.navigationStateChanged(state)

assertCommandNotIssued<Command.MakeOmnibarStickyIfNeeded>()
}

@Test
fun whenLoadingIsFinishedAndOmnibarIsAtTheBottomCheckIfWebViewMustBePadded() = runTest {
whenever(mockSettingsDataStore.omnibarPosition).thenReturn(BOTTOM)
val state = WebViewNavigationState(TestBackForwardList(), 100)

testee.browserViewState.value = browserViewState().copy(browserShowing = true)
testee.navigationStateChanged(state)

assertCommandIssued<Command.MakeOmnibarStickyIfNeeded>()
}

@Test
fun whenUrlUpdatedWithUrlYouTubeNoCookieThenReplaceUrlWithDuckPlayer() = runTest {
whenever(mockDuckPlayer.isSimulatedYoutubeNoCookie("https://youtube-nocookie.com/?videoID=1234".toUri())).thenReturn(true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2024 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.app.browser

import android.webkit.WebBackForwardList
import android.webkit.WebHistoryItem

class TestBackForwardList : WebBackForwardList() {
private val fakeHistory: MutableList<WebHistoryItem> = mutableListOf()
private var fakeCurrentIndex = -1

fun addPageToHistory(webHistoryItem: WebHistoryItem) {
fakeHistory.add(webHistoryItem)
fakeCurrentIndex++
}

override fun getSize() = fakeHistory.size

override fun getItemAtIndex(index: Int): WebHistoryItem = fakeHistory[index]

override fun getCurrentItem(): WebHistoryItem? = null

override fun getCurrentIndex(): Int = fakeCurrentIndex

override fun clone(): WebBackForwardList = throw NotImplementedError()
}
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ class CtaViewModelTest {

@Test
fun whenCtaShownIfCtaIsMarkedAsReadOnShowThenCtaInsertedInDatabase() {
testee.onCtaShown(OnboardingDaxDialogCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
testee.onCtaShown(OnboardingDaxDialogCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore, mockSettingsDataStore))

verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_END))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,21 @@ import androidx.lifecycle.lifecycleScope
import com.duckduckgo.anvil.annotations.ContributeToActivityStarter
import com.duckduckgo.anvil.annotations.InjectWith
import com.duckduckgo.app.appearance.AppearanceViewModel.Command
import com.duckduckgo.app.appearance.AppearanceViewModel.Command.LaunchAppIcon
import com.duckduckgo.app.appearance.AppearanceViewModel.Command.LaunchOmnibarPositionSettings
import com.duckduckgo.app.appearance.AppearanceViewModel.Command.LaunchThemeSettings
import com.duckduckgo.app.appearance.AppearanceViewModel.Command.UpdateTheme
import com.duckduckgo.app.browser.R
import com.duckduckgo.app.browser.databinding.ActivityAppearanceBinding
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.sendThemeChangedBroadcast
import com.duckduckgo.common.ui.view.dialog.RadioListAlertDialogBuilder
import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder
import com.duckduckgo.common.ui.view.gone
import com.duckduckgo.common.ui.view.show
import com.duckduckgo.common.ui.viewbinding.viewBinding
import com.duckduckgo.di.scopes.ActivityScope
import kotlinx.coroutines.flow.launchIn
Expand All @@ -46,7 +53,7 @@ class AppearanceActivity : DuckDuckGoActivity() {
private val viewModel: AppearanceViewModel by bindViewModel()
private val binding: ActivityAppearanceBinding by viewBinding()

private val forceDarkModeToggleListener = CompoundButton.OnCheckedChangeListener { view, isChecked ->
private val forceDarkModeToggleListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->
viewModel.onForceDarkModeSettingChanged(isChecked)

TextAlertDialogBuilder(this)
Expand Down Expand Up @@ -87,6 +94,7 @@ class AppearanceActivity : DuckDuckGoActivity() {
private fun configureUiEventHandlers() {
binding.selectedThemeSetting.setClickListener { viewModel.userRequestedToChangeTheme() }
binding.changeAppIconSetting.setOnClickListener { viewModel.userRequestedToChangeIcon() }
binding.addressBarPositionSetting.setOnClickListener { viewModel.userRequestedToChangeAddressBarPosition() }
}

private fun observeViewModel() {
Expand All @@ -99,6 +107,7 @@ class AppearanceActivity : DuckDuckGoActivity() {
binding.experimentalNightMode.quietlySetIsChecked(viewState.forceDarkModeEnabled, forceDarkModeToggleListener)
binding.experimentalNightMode.isEnabled = viewState.canForceDarkMode
binding.experimentalNightMode.isVisible = viewState.supportsForceDarkMode
updateSelectedOmnibarPosition(it.isOmnibarPositionFeatureEnabled, it.omnibarPosition)
}
}.launchIn(lifecycleScope)

Expand All @@ -119,11 +128,29 @@ class AppearanceActivity : DuckDuckGoActivity() {
binding.selectedThemeSetting.setSecondaryText(subtitle)
}

private fun updateSelectedOmnibarPosition(isFeatureEnabled: Boolean, position: OmnibarPosition) {
if (isFeatureEnabled) {
val subtitle = getString(
when (position) {
OmnibarPosition.TOP -> R.string.settingsAddressBarPositionTop
OmnibarPosition.BOTTOM -> R.string.settingsAddressBarPositionBottom
},
)
binding.addressBarPositionSetting.setSecondaryText(subtitle)
binding.addressBarPositionSettingDivider.show()
binding.addressBarPositionSetting.show()
} else {
binding.addressBarPositionSettingDivider.gone()
binding.addressBarPositionSetting.gone()
}
}

private fun processCommand(it: Command) {
when (it) {
is Command.LaunchAppIcon -> launchAppIconChange()
is Command.UpdateTheme -> sendThemeChangedBroadcast()
is Command.LaunchThemeSettings -> launchThemeSelector(it.theme)
is LaunchAppIcon -> launchAppIconChange()
is UpdateTheme -> sendThemeChangedBroadcast()
is LaunchThemeSettings -> launchThemeSelector(it.theme)
is LaunchOmnibarPositionSettings -> launchOmnibarPositionSelector(it.position)
}
}

Expand Down Expand Up @@ -159,4 +186,27 @@ class AppearanceActivity : DuckDuckGoActivity() {
)
.show()
}

private fun launchOmnibarPositionSelector(position: OmnibarPosition) {
RadioListAlertDialogBuilder(this)
.setTitle(R.string.settingsAddressBarPositionTitle)
.setOptions(
listOf(
R.string.settingsAddressBarPositionTop,
R.string.settingsAddressBarPositionBottom,
),
OmnibarPosition.entries.indexOf(position) + 1,
)
.setPositiveButton(R.string.dialogSave)
.setNegativeButton(R.string.cancel)
.addEventListener(
object : RadioListAlertDialogBuilder.EventListener() {
override fun onPositiveButtonClicked(selectedItem: Int) {
val newPosition = OmnibarPosition.entries[selectedItem - 1]
viewModel.onOmnibarPositionUpdated(newPosition)
}
},
)
.show()
}
}
Loading
Loading