diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserTimeBasedNotificationTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserBannerNotificationTest.kt similarity index 73% rename from app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserTimeBasedNotificationTest.kt rename to app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserBannerNotificationTest.kt index 223a5b815976..de51b2457c05 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserTimeBasedNotificationTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserBannerNotificationTest.kt @@ -19,7 +19,7 @@ package com.duckduckgo.app.browser.defaultBrowsing import com.duckduckgo.app.global.install.AppInstallStore import com.duckduckgo.app.statistics.Variant import com.duckduckgo.app.statistics.VariantManager -import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.ShowTimedReminder +import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.ShowBanner import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.whenever import org.junit.Assert.assertFalse @@ -28,7 +28,7 @@ import org.junit.Before import org.junit.Test import java.util.concurrent.TimeUnit -class DefaultBrowserTimeBasedNotificationTest { +class DefaultBrowserBannerNotificationTest { private lateinit var testee: DefaultBrowserTimeBasedNotification @@ -44,48 +44,55 @@ class DefaultBrowserTimeBasedNotificationTest { @Test fun whenDefaultBrowserNotSupportedByDeviceThenNotificationNotShown() { configureEnvironment(false, true, true, false) - assertFalse(testee.shouldShowNotification(browserShowing = true)) + assertFalse(testee.shouldShowBannerNotification(browserShowing = true)) } @Test fun whenDefaultBrowserFeatureNotSupportedThenNotificationNotShown() { configureEnvironment(true, false, true, false) - assertFalse(testee.shouldShowNotification(browserShowing = true)) + assertFalse(testee.shouldShowBannerNotification(browserShowing = true)) } @Test fun whenNoAppInstallTimeRecordedThenNotificationNotShown() { configureEnvironment(true, true, false, false) - assertFalse(testee.shouldShowNotification(browserShowing = true)) + assertFalse(testee.shouldShowBannerNotification(browserShowing = true)) } @Test fun whenUserDeclinedPreviouslyThenNotificationNotShown() { configureEnvironment(true, true, true, true) - assertFalse(testee.shouldShowNotification(browserShowing = true)) + assertFalse(testee.shouldShowBannerNotification(browserShowing = true)) } @Test fun whenNotEnoughTimeHasPassedSinceInstallThenNotificationNotShown() { configureEnvironment(true, true, true, false) whenever(appInstallStore.installTimestamp).thenReturn(0) - assertFalse(testee.shouldShowNotification(browserShowing = true, timeNow = TimeUnit.SECONDS.toMillis(10))) + assertFalse(testee.shouldShowBannerNotification(browserShowing = true, timeNow = TimeUnit.SECONDS.toMillis(10))) } @Test fun whenEnoughTimeHasPassedSinceInstallThenNotificationShown() { configureEnvironment(true, true, true, false) whenever(appInstallStore.installTimestamp).thenReturn(0) - assertTrue(testee.shouldShowNotification(browserShowing = true, timeNow = TimeUnit.DAYS.toMillis(100))) + assertTrue(testee.shouldShowBannerNotification(browserShowing = true, timeNow = TimeUnit.DAYS.toMillis(100))) + } + + @Test + fun whenUserDeclinedHomeScreenCallToActionPreviouslyThenNotificationStillShown() { + configureEnvironment(true, true, true, false) + whenever(appInstallStore.hasUserDeclinedDefaultBrowserHomeScreenCallToActionPreviously()).thenReturn(true) + assertTrue(testee.shouldShowBannerNotification(browserShowing = true)) } private fun configureEnvironment(deviceSupported: Boolean, featureEnabled: Boolean, timestampRecorded: Boolean, previousDecline: Boolean) { whenever(mockDetector.deviceSupportsDefaultBrowserConfiguration()).thenReturn(deviceSupported) whenever(variantManager.getVariant()).thenReturn(if (featureEnabled) variantWithFeatureEnabled() else variantWithFeatureDisabled()) whenever(appInstallStore.hasInstallTimestampRecorded()).thenReturn(timestampRecorded) - whenever(appInstallStore.hasUserDeclinedDefaultBrowserPreviously()).thenReturn(previousDecline) + whenever(appInstallStore.hasUserDeclinedDefaultBrowserBannerPreviously()).thenReturn(previousDecline) } - private fun variantWithFeatureEnabled() = Variant("", 0.0, listOf(ShowTimedReminder)) + private fun variantWithFeatureEnabled() = Variant("", 0.0, listOf(ShowBanner)) private fun variantWithFeatureDisabled() = Variant("", 0.0, listOf()) } \ No newline at end of file diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserHomeScreenCallToActionTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserHomeScreenCallToActionTest.kt new file mode 100644 index 000000000000..229dd1291c64 --- /dev/null +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserHomeScreenCallToActionTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 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.defaultBrowsing + +import com.duckduckgo.app.global.install.AppInstallStore +import com.duckduckgo.app.statistics.Variant +import com.duckduckgo.app.statistics.VariantManager +import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.ShowHomeScreenCallToAction +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.whenever +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class DefaultBrowserHomeScreenCallToActionTest { + + private lateinit var testee: DefaultBrowserTimeBasedNotification + + private val mockDetector: DefaultBrowserDetector = mock() + private val appInstallStore: AppInstallStore = mock() + private val variantManager: VariantManager = mock() + + @Before + fun setup() { + testee = DefaultBrowserTimeBasedNotification(mockDetector, appInstallStore, variantManager) + } + + @Test + fun whenDefaultBrowserNotSupportedByDeviceThenCallToActionNotShown() { + configureEnvironment(false, true, true, false) + assertFalse(testee.shouldShowHomeScreenCallToActionNotification()) + } + + @Test + fun whenDefaultBrowserFeatureNotSupportedThenCallToActionNotShown() { + configureEnvironment(true, false, true, false) + assertFalse(testee.shouldShowHomeScreenCallToActionNotification( )) + } + + @Test + fun whenNoAppInstallTimeRecordedThenCallToActionNotShown() { + configureEnvironment(true, true, false, false) + assertFalse(testee.shouldShowHomeScreenCallToActionNotification( )) + } + + @Test + fun whenUserDeclinedPreviouslyThenCallToActionNotShown() { + configureEnvironment(true, true, true, true) + assertFalse(testee.shouldShowHomeScreenCallToActionNotification( )) + } + + @Test + fun whenAllOtherConditionsPassThenCallToActionShown() { + configureEnvironment(true, true, true, false) + whenever(appInstallStore.installTimestamp).thenReturn(0) + assertTrue(testee.shouldShowHomeScreenCallToActionNotification()) + } + + private fun configureEnvironment(deviceSupported: Boolean, featureEnabled: Boolean, timestampRecorded: Boolean, previousDecline: Boolean) { + whenever(mockDetector.deviceSupportsDefaultBrowserConfiguration()).thenReturn(deviceSupported) + whenever(variantManager.getVariant()).thenReturn(if (featureEnabled) variantWithFeatureEnabled() else variantWithFeatureDisabled()) + whenever(appInstallStore.hasInstallTimestampRecorded()).thenReturn(timestampRecorded) + whenever(appInstallStore.hasUserDeclinedDefaultBrowserHomeScreenCallToActionPreviously()).thenReturn(previousDecline) + } + + private fun variantWithFeatureEnabled() = Variant("", 0.0, listOf(ShowHomeScreenCallToAction)) + private fun variantWithFeatureDisabled() = Variant("", 0.0, listOf()) +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/duckduckgo/app/global/install/AppInstallSharedPreferencesTest.kt b/app/src/androidTest/java/com/duckduckgo/app/global/install/AppInstallSharedPreferencesTest.kt index a118317b3b6d..71a69b1a766a 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/global/install/AppInstallSharedPreferencesTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/global/install/AppInstallSharedPreferencesTest.kt @@ -36,8 +36,13 @@ class AppInstallSharedPreferencesTest { } @Test - fun whenInitializedThenUserHasNotBeenMarkedAsHavingPreviouslyDeclined() { - assertFalse(testee.hasUserDeclinedDefaultBrowserPreviously()) + fun whenInitializedThenUserHasNotBeenMarkedAsHavingPreviouslyDeclinedBanner() { + assertFalse(testee.hasUserDeclinedDefaultBrowserBannerPreviously()) + } + + @Test + fun whenInitializedThenUserHasNotBeenMarkedAsHavingPreviouslyDeclinedHomeScreenCallToAction() { + assertFalse(testee.hasUserDeclinedDefaultBrowserHomeScreenCallToActionPreviously()) } @Test @@ -60,15 +65,17 @@ class AppInstallSharedPreferencesTest { } @Test - fun whenUserPreviouslyDeclinedThenThatIsReturnedWhenQueried() { + fun whenUserPreviouslyDeclinedBannerThenThatIsReturnedWhenQueried() { val timestamp = 1L - testee.recordUserDeclinedToSetDefaultBrowser(timestamp) - assertTrue(testee.hasUserDeclinedDefaultBrowserPreviously()) + testee.recordUserDeclinedBannerToSetDefaultBrowser(timestamp) + assertTrue(testee.hasUserDeclinedDefaultBrowserBannerPreviously()) } @Test - fun whenUserNotPreviouslyDeclinedThenThatIsReturnedWhenQueried() { - assertFalse(testee.hasUserDeclinedDefaultBrowserPreviously()) + fun whenUserPreviouslyDeclinedHomeScreenCallToActionThenThatIsReturnedWhenQueried() { + val timestamp = 1L + testee.recordUserDeclinedHomeScreenCallToActionToSetDefaultBrowser(timestamp) + assertTrue(testee.hasUserDeclinedDefaultBrowserHomeScreenCallToActionPreviously()) } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/duckduckgo/app/statistics/ExperimentationVariantManagerTest.kt b/app/src/androidTest/java/com/duckduckgo/app/statistics/ExperimentationVariantManagerTest.kt index 3672474a33c8..f4329fa6bb85 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/statistics/ExperimentationVariantManagerTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/statistics/ExperimentationVariantManagerTest.kt @@ -16,6 +16,8 @@ package com.duckduckgo.app.statistics +import android.os.Build +import android.support.test.filters.SdkSuppress import com.duckduckgo.app.statistics.store.StatisticsDataStore import com.nhaarman.mockito_kotlin.* import org.junit.Assert.assertEquals @@ -93,7 +95,8 @@ class ExperimentationVariantManagerTest { @Test - fun whenNoVariantPersistedThenNewVariantAllocated() { + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) + fun whenNougatOrLaterAndNoVariantPersistedThenNewVariantAllocated() { activeVariants.add(Variant("foo", 100.0)) testee.getVariant(activeVariants) @@ -102,11 +105,30 @@ class ExperimentationVariantManagerTest { } @Test - fun whenNoVariantPersistedThenNewVariantKeyIsAllocatedAndPersisted() { + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) + fun whenNougatOrLaterAndNoVariantPersistedThenNewVariantKeyIsAllocatedAndPersisted() { activeVariants.add(Variant("foo", 100.0)) testee.getVariant(activeVariants) verify(mockStore).variant = "foo" } + + @Test + @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M) + fun whenMarshmallowOrEarlierAndNoVariantPersistedThenDefaultVariantAllocated() { + activeVariants.add(Variant("foo", 100.0)) + + assertEquals(VariantManager.DEFAULT_VARIANT, testee.getVariant(activeVariants)) + } + + @Test + @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M) + fun whenMarshmallowOrEarlierAndNoVariantPersistedThenDefaultVariantKeyIsAllocatedAndPersisted() { + activeVariants.add(Variant("foo", 100.0)) + + testee.getVariant(activeVariants) + + verify(mockStore).variant = VariantManager.DEFAULT_VARIANT.key + } } \ No newline at end of file diff --git a/app/src/androidTest/java/com/duckduckgo/app/statistics/VariantManagerTest.kt b/app/src/androidTest/java/com/duckduckgo/app/statistics/VariantManagerTest.kt index e64722564f41..0dda62e6ce98 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/statistics/VariantManagerTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/statistics/VariantManagerTest.kt @@ -16,8 +16,8 @@ package com.duckduckgo.app.statistics -import org.junit.Assert.assertNotNull -import org.junit.Assert.fail +import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.* +import org.junit.Assert.* import org.junit.Test class VariantManagerTest { @@ -26,24 +26,43 @@ class VariantManagerTest { private val totalWeight = variants.sumByDouble { it.weight } @Test - fun whenChanceOfControlVariantCalculatedThenOddsAreOneInTwo() { - val variant = variants.firstOrNull { it.key == "my" } - assertNotNull(variant) - assertEqualsDouble( 0.5, variant!!.weight / totalWeight) + fun onboardingOnlyVariantConfiguredCorrectly() { + val variant = variants.firstOrNull { it.key == "ms" } + assertEqualsDouble( 0.20, variant!!.weight / totalWeight) + assertTrue(variant.hasFeature(ShowInOnboarding)) + assertEquals(1, variant.features.size) + } + + @Test + fun homeScreenCallToActionVariantConfiguredCorrectly() { + val variant = variants.firstOrNull { it.key == "mt" } + assertEqualsDouble( 0.20, variant!!.weight / totalWeight) + assertTrue(variant.hasFeature(ShowHomeScreenCallToAction)) + assertEquals(1, variant.features.size) } @Test - fun whenChanceOfOnboardingOnlyVariantCalculatedThenOddsAreOneInFour() { - val variant = variants.firstOrNull { it.key == "mw" } - assertNotNull(variant) - assertEqualsDouble( 0.25, variant!!.weight / totalWeight) + fun showBannerVariantConfiguredCorrectly() { + val variant = variants.firstOrNull { it.key == "mu" } + assertEqualsDouble( 0.20, variant!!.weight / totalWeight) + assertTrue(variant.hasFeature(ShowBanner)) + assertEquals(1, variant.features.size) } @Test - fun whenChanceOfOnboardingAndReminderVariantCalculatedThenOddsAreOneInFour() { - val variant = variants.firstOrNull { it.key == "mx" } - assertNotNull(variant) - assertEqualsDouble( 0.25, variant!!.weight / totalWeight) + fun showBannerAndShowHomeScreenCallToActionVariantConfiguredCorrectly() { + val variant = variants.firstOrNull { it.key == "mv" } + assertEqualsDouble( 0.20, variant!!.weight / totalWeight) + assertTrue(variant.hasFeature(ShowBanner)) + assertTrue(variant.hasFeature(ShowHomeScreenCallToAction)) + assertEquals(2, variant.features.size) + } + + @Test + fun controlVariantConfiguredCorrectly() { + val variant = variants.firstOrNull { it.key == "my" } + assertEqualsDouble( 0.2, variant!!.weight / totalWeight) + assertEquals(0, variant.features.size) } private fun assertEqualsDouble(expected: Double, actual: Double) { 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 36d7e5074dbc..59b7c1791fca 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -18,6 +18,7 @@ package com.duckduckgo.app.browser import android.Manifest import android.animation.LayoutTransition.CHANGING +import android.animation.LayoutTransition.DISAPPEARING import android.annotation.SuppressLint import android.app.Activity.RESULT_OK import android.app.ActivityOptions @@ -70,6 +71,8 @@ import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.fragment_browser_tab.* import kotlinx.android.synthetic.main.include_banner_notification.* import kotlinx.android.synthetic.main.include_find_in_page.* +import kotlinx.android.synthetic.main.include_home_screen_default_browser_call_to_action.* +import kotlinx.android.synthetic.main.include_new_browser_tab.* import kotlinx.android.synthetic.main.include_omnibar_toolbar.* import kotlinx.android.synthetic.main.include_omnibar_toolbar.view.* import kotlinx.android.synthetic.main.popup_window_browser_menu.view.* @@ -153,6 +156,8 @@ class BrowserTabFragment : Fragment(), FindListener { } } + private val logoHidingLayoutChangeListener by lazy { LogoHidingLayoutChangeListener(ddgLogo, homeScreenCallToActionContainer) } + override fun onAttach(context: Context?) { AndroidSupportInjection.inject(this) super.onAttach(context) @@ -173,6 +178,7 @@ class BrowserTabFragment : Fragment(), FindListener { configureObservers() configureToolbar() configureBannerNotification() + configureCallToActionButton() configureWebView() viewModel.registerWebViewListener(webViewClient, webChromeClient) configureOmnibarTextInput() @@ -219,6 +225,10 @@ class BrowserTabFragment : Fragment(), FindListener { it?.let { renderer.renderAutocomplete(it) } }) + viewModel.globalLayoutState.observe(this, Observer { + it?.let { renderer.renderGlobalViewState(it) } + }) + viewModel.browserViewState.observe(this, Observer { it?.let { renderer.renderBrowserViewState(it) } }) @@ -315,7 +325,6 @@ class BrowserTabFragment : Fragment(), FindListener { is Command.ShowFileChooser -> { launchFilePicker(it) } - is Command.LaunchDefaultAppSystemSettings -> { launchDefaultAppSystemSettings() } } } @@ -390,13 +399,22 @@ class BrowserTabFragment : Fragment(), FindListener { private fun configureBannerNotification() { dismissBannerButton.setOnClickListener { - viewModel.userDeclinedToSetAsDefaultBrowser() + viewModel.userDeclinedBannerToSetAsDefaultBrowser() } bannerNotification.setOnClickListener { - viewModel.userAcceptedToSetAsDefaultBrowser() + launchDefaultAppSystemSettingsFromBanner() } } + private fun configureCallToActionButton() { + homeScreenCallToActionContainer.setOnClickListener { + launchDefaultAppSystemSettingsFromCallToActionButton() + } + + homeScreenCallToActionDismissButton.setOnClickListener { + viewModel.userDeclinedHomeScreenCallToActionToSetAsDefaultBrowser() + } + } private fun configureFindInPage() { findInPageInput.setOnFocusChangeListener { _, hasFocus -> @@ -438,7 +456,11 @@ class BrowserTabFragment : Fragment(), FindListener { } private fun configureKeyboardAwareLogoAnimation() { - logoParent.layoutTransition.enableTransitionType(CHANGING) + // we want layout transitions for when the size changes; we don't want them when items disappear (can cause glitch on call to action button) + newTabLayout.layoutTransition?.enableTransitionType(CHANGING) + newTabLayout.layoutTransition?.disableTransitionType(DISAPPEARING) + + rootView.addOnLayoutChangeListener(logoHidingLayoutChangeListener) } private fun userEnteredQuery(query: String) { @@ -513,7 +535,7 @@ class BrowserTabFragment : Fragment(), FindListener { activity?.share(url, "") } - private fun launchDefaultAppSystemSettings() { + private fun launchDefaultAppSystemSettingsFromBanner() { activity?.let { val options = ActivityOptions.makeSceneTransitionAnimation(it, bannerNotification, "defaultBrowserBannerTransition") val intent = DefaultBrowserInfoActivity.intent(it) @@ -521,6 +543,13 @@ class BrowserTabFragment : Fragment(), FindListener { } } + private fun launchDefaultAppSystemSettingsFromCallToActionButton() { + activity?.let { + val intent = DefaultBrowserInfoActivity.intent(it) + startActivity(intent) + } + } + private fun addBookmark() { val addBookmarkDialog = SaveBookmarkDialogFragment.createDialogCreationMode( existingTitle = webView?.title, @@ -730,6 +759,7 @@ class BrowserTabFragment : Fragment(), FindListener { private var lastSeenLoadingViewState: LoadingViewState? = null private var lastSeenFindInPageViewState: FindInPageViewState? = null private var lastSeenBrowserViewState: BrowserViewState? = null + private var lastSeenGlobalViewState: GlobalLayoutViewState? = null private var lastSeenDefaultBrowserViewState: DefaultBrowserViewState? = null private var lastSeenAutoCompleteViewState: AutoCompleteViewState? = null @@ -742,6 +772,14 @@ class BrowserTabFragment : Fragment(), FindListener { } else { bannerNotification.gone() } + + if (viewState.showHomeScreenCallToActionButton) { + homeScreenCallToActionContainer.show() + } else { + homeScreenCallToActionContainer.gone() + } + + logoHidingLayoutChangeListener.update() } } @@ -787,6 +825,18 @@ class BrowserTabFragment : Fragment(), FindListener { } } + fun renderGlobalViewState(viewState: GlobalLayoutViewState) { + renderIfChanged(viewState, lastSeenGlobalViewState) { + if (viewState.isNewTabState) { + newTabLayout.show() + browserLayout.hide() + } else { + newTabLayout.hide() + browserLayout.show() + } + } + } + fun renderBrowserViewState(viewState: BrowserViewState) { renderIfChanged(viewState, lastSeenBrowserViewState) { lastSeenBrowserViewState = viewState diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt index 8222634d09a9..e2733a5e4c23 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt @@ -80,6 +80,10 @@ class BrowserTabViewModel( appConfigurationDao: AppConfigurationDao ) : WebViewClientListener, SaveBookmarkListener, ViewModel() { + data class GlobalLayoutViewState( + val isNewTabState: Boolean = true + ) + data class BrowserViewState( val browserShowing: Boolean = false, val isFullScreen: Boolean = false, @@ -120,7 +124,8 @@ class BrowserTabViewModel( ) data class DefaultBrowserViewState( - val showDefaultBrowserBanner: Boolean = false + val showDefaultBrowserBanner: Boolean = false, + val showHomeScreenCallToActionButton: Boolean = false ) sealed class Command { @@ -141,11 +146,11 @@ class BrowserTabViewModel( class DisplayMessage(@StringRes val messageId: Int) : Command() object DismissFindInPage : Command() class ShowFileChooser(val filePathCallback: ValueCallback>, val fileChooserParams: WebChromeClient.FileChooserParams) : Command() - object LaunchDefaultAppSystemSettings : Command() } val autoCompleteViewState: MutableLiveData = MutableLiveData() val browserViewState: MutableLiveData = MutableLiveData() + val globalLayoutState: MutableLiveData = MutableLiveData() val loadingViewState: MutableLiveData = MutableLiveData() val omnibarViewState: MutableLiveData = MutableLiveData() val defaultBrowserViewState: MutableLiveData = MutableLiveData() @@ -227,8 +232,9 @@ class BrowserTabViewModel( fun onViewVisible() { command.value = if (url.value == null) ShowKeyboard else Command.HideKeyboard - val showBanner = defaultBrowserNotification.shouldShowNotification(currentBrowserViewState().browserShowing) - defaultBrowserViewState.value = DefaultBrowserViewState(showBanner) + val showBanner = defaultBrowserNotification.shouldShowBannerNotification(currentBrowserViewState().browserShowing) + val showCallToActionButton = defaultBrowserNotification.shouldShowHomeScreenCallToActionNotification() + defaultBrowserViewState.value = DefaultBrowserViewState(showBanner, showCallToActionButton) } fun onUserSubmittedQuery(input: String) { @@ -240,6 +246,7 @@ class BrowserTabViewModel( val trimmedInput = input.trim() url.value = queryUrlConverter.convertQueryToUrl(trimmedInput) + globalLayoutState.value = GlobalLayoutViewState(isNewTabState = false) findInPageViewState.value = FindInPageViewState(visible = false, canFindInPage = true) omnibarViewState.value = currentOmnibarViewState().copy(omnibarText = trimmedInput) browserViewState.value = currentBrowserViewState().copy(browserShowing = true, showClearButton = false) @@ -341,7 +348,8 @@ class BrowserTabViewModel( ) if (duckDuckGoUrlDetector.isDuckDuckGoQueryUrl(url)) { - defaultBrowserViewState.value = currentDefaultBrowserViewState().copy(showDefaultBrowserBanner = defaultBrowserNotification.shouldShowNotification(currentBrowserViewState.browserShowing)) + val shouldShowBanner = defaultBrowserNotification.shouldShowBannerNotification(currentBrowserViewState.browserShowing) + defaultBrowserViewState.value = currentDefaultBrowserViewState().copy(showDefaultBrowserBanner = shouldShowBanner) statisticsUpdater.refreshRetentionAtb() } @@ -525,7 +533,8 @@ class BrowserTabViewModel( } private fun initializeViewStates() { - defaultBrowserViewState.value = DefaultBrowserViewState() + globalLayoutState.value = GlobalLayoutViewState() + defaultBrowserViewState.value = DefaultBrowserViewState(showHomeScreenCallToActionButton = defaultBrowserNotification.shouldShowHomeScreenCallToActionNotification()) browserViewState.value = BrowserViewState() loadingViewState.value = LoadingViewState() autoCompleteViewState.value = AutoCompleteViewState() @@ -539,16 +548,17 @@ class BrowserTabViewModel( } } - fun userDeclinedToSetAsDefaultBrowser() { - defaultBrowserDetector.userDeclinedToSetAsDefaultBrowser() + fun userDeclinedBannerToSetAsDefaultBrowser() { + defaultBrowserDetector.userDeclinedBannerToSetAsDefaultBrowser() val currentDefaultBrowserViewState = currentDefaultBrowserViewState() defaultBrowserViewState.value = currentDefaultBrowserViewState.copy(showDefaultBrowserBanner = false) } - fun userAcceptedToSetAsDefaultBrowser() { - command.value = LaunchDefaultAppSystemSettings + fun userDeclinedHomeScreenCallToActionToSetAsDefaultBrowser() { + defaultBrowserDetector.userDeclinedHomeScreenCallToActionToSetAsDefaultBrowser() + val currentDefaultBrowserViewState = currentDefaultBrowserViewState() + defaultBrowserViewState.value = currentDefaultBrowserViewState.copy(showHomeScreenCallToActionButton = false) } - } diff --git a/app/src/main/java/com/duckduckgo/app/browser/LogoHidingLayoutChangeListener.kt b/app/src/main/java/com/duckduckgo/app/browser/LogoHidingLayoutChangeListener.kt new file mode 100644 index 000000000000..604447e2dcdc --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/browser/LogoHidingLayoutChangeListener.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 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.graphics.Rect +import android.view.View +import androidx.core.view.isVisible +import com.duckduckgo.app.global.view.gone +import com.duckduckgo.app.global.view.show +import com.duckduckgo.app.global.view.toDp +import timber.log.Timber + + +class LogoHidingLayoutChangeListener(private val ddgLogoView: View, private val callToActionButton: View) : View.OnLayoutChangeListener { + + override fun onLayoutChange(view: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) { + update() + } + + fun update() { + val heightDp = getHeightDp() + + Timber.v("App height now: $heightDp dp, call to action button showing: ${callToActionButton.isVisible}") + + if (enoughRoomForLogo(heightDp)) { + ddgLogoView.show() + } else { + ddgLogoView.gone() + } + } + + private fun getHeightDp(): Int { + val r = Rect() + ddgLogoView.getWindowVisibleDisplayFrame(r) + return r.height().toDp() + } + + private fun enoughRoomForLogo(heightDp: Int): Boolean { + val minimumHeight = if (callToActionButton.isVisible) { + MINIMUM_HEIGHT_REQUIRED_CALL_TO_ACTION_SHOWING + } else { + MINIMUM_HEIGHT_REQUIRED_CALL_TO_ACTION_HIDDEN + } + return heightDp >= minimumHeight + } + + companion object { + private const val MINIMUM_HEIGHT_REQUIRED_CALL_TO_ACTION_SHOWING = 230 + private const val MINIMUM_HEIGHT_REQUIRED_CALL_TO_ACTION_HIDDEN = 150 + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserDetector.kt b/app/src/main/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserDetector.kt index 128014aec168..e8888f5192d1 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserDetector.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserDetector.kt @@ -31,7 +31,8 @@ import javax.inject.Inject interface DefaultBrowserDetector { fun deviceSupportsDefaultBrowserConfiguration(): Boolean fun isCurrentlyConfiguredAsDefaultBrowser(): Boolean - fun userDeclinedToSetAsDefaultBrowser(timestamp: Long = System.currentTimeMillis()) + fun userDeclinedBannerToSetAsDefaultBrowser(timestamp: Long = System.currentTimeMillis()) + fun userDeclinedHomeScreenCallToActionToSetAsDefaultBrowser(timestamp: Long = System.currentTimeMillis()) } class AndroidDefaultBrowserDetector @Inject constructor(private val context: Context, private val appInstallStore: AppInstallStore) : @@ -50,7 +51,11 @@ class AndroidDefaultBrowserDetector @Inject constructor(private val context: Con return defaultAlready } - override fun userDeclinedToSetAsDefaultBrowser(timestamp: Long) { - appInstallStore.recordUserDeclinedToSetDefaultBrowser(timestamp) + override fun userDeclinedBannerToSetAsDefaultBrowser(timestamp: Long) { + appInstallStore.recordUserDeclinedBannerToSetDefaultBrowser(timestamp) + } + + override fun userDeclinedHomeScreenCallToActionToSetAsDefaultBrowser(timestamp: Long) { + appInstallStore.recordUserDeclinedHomeScreenCallToActionToSetDefaultBrowser(timestamp) } } \ No newline at end of file diff --git a/app/src/main/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserNotification.kt b/app/src/main/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserNotification.kt index 46b117d4f52a..2a759b6d4b1d 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserNotification.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/defaultBrowsing/DefaultBrowserNotification.kt @@ -20,16 +20,19 @@ import com.duckduckgo.app.browser.BuildConfig import com.duckduckgo.app.global.install.AppInstallStore import com.duckduckgo.app.statistics.VariantManager import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature -import timber.log.Timber +import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.ShowBanner +import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.ShowHomeScreenCallToAction import java.util.concurrent.TimeUnit import javax.inject.Inject interface DefaultBrowserNotification { - fun shouldShowNotification( + fun shouldShowBannerNotification( browserShowing: Boolean, timeNow: Long = System.currentTimeMillis() ): Boolean + + fun shouldShowHomeScreenCallToActionNotification(): Boolean } class DefaultBrowserTimeBasedNotification @Inject constructor( @@ -38,7 +41,7 @@ class DefaultBrowserTimeBasedNotification @Inject constructor( private val variantManager: VariantManager ) : DefaultBrowserNotification { - override fun shouldShowNotification(browserShowing: Boolean, timeNow: Long): Boolean { + override fun shouldShowBannerNotification(browserShowing: Boolean, timeNow: Long): Boolean { if (!browserShowing) { return false @@ -48,7 +51,7 @@ class DefaultBrowserTimeBasedNotification @Inject constructor( return false } - if (!isFeatureEnabled()) { + if (!isFeatureEnabled(ShowBanner)) { return false } @@ -56,39 +59,50 @@ class DefaultBrowserTimeBasedNotification @Inject constructor( return false } - if (hasUserDeclinedPreviously()) { + if (appInstallStore.hasUserDeclinedDefaultBrowserBannerPreviously()) { return false } return hasEnoughTimeElapsed(timeNow) } + override fun shouldShowHomeScreenCallToActionNotification(): Boolean { + + if (!isDeviceCapable()) { + return false + } + + if (!isFeatureEnabled(ShowHomeScreenCallToAction)) { + return false + } + + if (isAlreadyDefaultBrowser()) { + return false + } + + if (appInstallStore.hasUserDeclinedDefaultBrowserHomeScreenCallToActionPreviously()) { + return false + } + + return true + } + private fun isDeviceCapable(): Boolean { return defaultBrowserDetector.deviceSupportsDefaultBrowserConfiguration() && appInstallStore.hasInstallTimestampRecorded() } - private fun isFeatureEnabled(): Boolean { - return variantManager.getVariant().hasFeature(DefaultBrowserFeature.ShowTimedReminder) + private fun isFeatureEnabled(feature: DefaultBrowserFeature): Boolean { + return variantManager.getVariant().hasFeature(feature) } private fun isAlreadyDefaultBrowser(): Boolean { return defaultBrowserDetector.isCurrentlyConfiguredAsDefaultBrowser() } - private fun hasUserDeclinedPreviously(): Boolean { - return appInstallStore.hasUserDeclinedDefaultBrowserPreviously() - } - private fun hasEnoughTimeElapsed(now: Long): Boolean { val elapsed = calculateElapsedTime(now) - return if (elapsed >= ELAPSED_TIME_THRESHOLD_MS) { - Timber.v("Enough time has elapsed to show banner") - true - } else { - Timber.v("Not enough time has elapsed to show banner") - false - } + return elapsed >= ELAPSED_TIME_THRESHOLD_MS } private fun calculateElapsedTime(now: Long): Long { diff --git a/app/src/main/java/com/duckduckgo/app/global/install/AppInstallStore.kt b/app/src/main/java/com/duckduckgo/app/global/install/AppInstallStore.kt index b14343b3d7e9..bb4cda8d129b 100644 --- a/app/src/main/java/com/duckduckgo/app/global/install/AppInstallStore.kt +++ b/app/src/main/java/com/duckduckgo/app/global/install/AppInstallStore.kt @@ -27,9 +27,10 @@ interface AppInstallStore { var installTimestamp: Long fun hasInstallTimestampRecorded() : Boolean - fun recordUserDeclinedToSetDefaultBrowser(timestamp: Long = System.currentTimeMillis()) - fun hasUserDeclinedDefaultBrowserPreviously(): Boolean - fun clearUserDeclineState() + fun recordUserDeclinedBannerToSetDefaultBrowser(timestamp: Long = System.currentTimeMillis()) + fun recordUserDeclinedHomeScreenCallToActionToSetDefaultBrowser(timestamp: Long = System.currentTimeMillis()) + fun hasUserDeclinedDefaultBrowserBannerPreviously(): Boolean + fun hasUserDeclinedDefaultBrowserHomeScreenCallToActionPreviously(): Boolean } class AppInstallSharedPreferences @Inject constructor(private val context: Context) : AppInstallStore { @@ -39,18 +40,24 @@ class AppInstallSharedPreferences @Inject constructor(private val context: Conte override fun hasInstallTimestampRecorded(): Boolean = preferences.contains(KEY_TIMESTAMP_UTC) - override fun recordUserDeclinedToSetDefaultBrowser(timestamp: Long) { + override fun recordUserDeclinedBannerToSetDefaultBrowser(timestamp: Long) { preferences.edit { - putLong(KEY_TIMESTAMP_USER_DECLINED_DEFAULT_BROWSER, timestamp) + putLong(KEY_TIMESTAMP_USER_DECLINED_BANNER_DEFAULT_BROWSER, timestamp) } } - override fun hasUserDeclinedDefaultBrowserPreviously(): Boolean { - return preferences.contains(KEY_TIMESTAMP_USER_DECLINED_DEFAULT_BROWSER) + override fun recordUserDeclinedHomeScreenCallToActionToSetDefaultBrowser(timestamp: Long) { + preferences.edit { + putLong(KEY_TIMESTAMP_USER_DECLINED_CALL_TO_ACTION_DEFAULT_BROWSER, timestamp) + } + } + + override fun hasUserDeclinedDefaultBrowserBannerPreviously(): Boolean { + return preferences.contains(KEY_TIMESTAMP_USER_DECLINED_BANNER_DEFAULT_BROWSER) } - override fun clearUserDeclineState() { - preferences.edit { remove(KEY_TIMESTAMP_USER_DECLINED_DEFAULT_BROWSER) } + override fun hasUserDeclinedDefaultBrowserHomeScreenCallToActionPreviously(): Boolean { + return preferences.contains(KEY_TIMESTAMP_USER_DECLINED_CALL_TO_ACTION_DEFAULT_BROWSER) } private val preferences: SharedPreferences @@ -61,6 +68,7 @@ class AppInstallSharedPreferences @Inject constructor(private val context: Conte @VisibleForTesting const val FILENAME = "com.duckduckgo.app.install.settings" const val KEY_TIMESTAMP_UTC = "INSTALL_TIMESTAMP_UTC" - const val KEY_TIMESTAMP_USER_DECLINED_DEFAULT_BROWSER = "USER_DECLINED_DEFAULT_BROWSER_TIMESTAMP_UTC" + const val KEY_TIMESTAMP_USER_DECLINED_BANNER_DEFAULT_BROWSER = "USER_DECLINED_DEFAULT_BROWSER_TIMESTAMP_UTC" + const val KEY_TIMESTAMP_USER_DECLINED_CALL_TO_ACTION_DEFAULT_BROWSER = "USER_DECLINED_DEFAULT_BROWSER_CALL_TO_ACTION_TIMESTAMP_UTC" } } \ No newline at end of file diff --git a/app/src/main/java/com/duckduckgo/app/statistics/VariantManager.kt b/app/src/main/java/com/duckduckgo/app/statistics/VariantManager.kt index 78bda521b257..572a23e0d5a5 100644 --- a/app/src/main/java/com/duckduckgo/app/statistics/VariantManager.kt +++ b/app/src/main/java/com/duckduckgo/app/statistics/VariantManager.kt @@ -16,9 +16,10 @@ package com.duckduckgo.app.statistics +import android.os.Build import android.support.annotation.WorkerThread -import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.ShowInOnboarding -import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.ShowTimedReminder +import com.duckduckgo.app.statistics.VariantManager.Companion.DEFAULT_VARIANT +import com.duckduckgo.app.statistics.VariantManager.VariantFeature.DefaultBrowserFeature.* import com.duckduckgo.app.statistics.store.StatisticsDataStore import timber.log.Timber @@ -29,7 +30,8 @@ interface VariantManager { sealed class DefaultBrowserFeature : VariantFeature() { object ShowInOnboarding : DefaultBrowserFeature() - object ShowTimedReminder : DefaultBrowserFeature() + object ShowBanner : DefaultBrowserFeature() + object ShowHomeScreenCallToAction : DefaultBrowserFeature() } } @@ -39,9 +41,13 @@ interface VariantManager { val DEFAULT_VARIANT = Variant(key = "", features = emptyList()) val ACTIVE_VARIANTS = listOf( - Variant(key = "mw", weight = 25.0, features = listOf(ShowInOnboarding)), - Variant(key = "mx", weight = 25.0, features = listOf(ShowInOnboarding, ShowTimedReminder)), - Variant(key = "my", weight = 50.0, features = emptyList()) + Variant(key = "ms", weight = 20.0, features = listOf(ShowInOnboarding)), + Variant(key = "mt", weight = 20.0, features = listOf(ShowHomeScreenCallToAction)), + Variant(key = "mu", weight = 20.0, features = listOf(ShowBanner)), + Variant(key = "mv", weight = 20.0, features = listOf(ShowBanner, ShowHomeScreenCallToAction)), + + // control group + Variant(key = "my", weight = 20.0, features = emptyList()) ) } @@ -53,13 +59,14 @@ class ExperimentationVariantManager( private val indexRandomizer: IndexRandomizer ) : VariantManager { + @Synchronized override fun getVariant(activeVariants: List): Variant { - if (activeVariants.isEmpty()) return VariantManager.DEFAULT_VARIANT + if (activeVariants.isEmpty()) return DEFAULT_VARIANT val currentVariantKey = store.variant - if (currentVariantKey == VariantManager.DEFAULT_VARIANT.key) { - return VariantManager.DEFAULT_VARIANT + if (currentVariantKey == DEFAULT_VARIANT.key) { + return DEFAULT_VARIANT } if (currentVariantKey == null) { @@ -72,7 +79,7 @@ class ExperimentationVariantManager( val currentVariant = lookupVariant(currentVariantKey, activeVariants) if (currentVariant == null) { Timber.i("Variant $currentVariantKey no longer an active variant; user will now use default variant") - val newVariant = VariantManager.DEFAULT_VARIANT + val newVariant = DEFAULT_VARIANT persistVariant(newVariant) return newVariant } @@ -88,6 +95,11 @@ class ExperimentationVariantManager( } private fun generateVariant(activeVariants: List): Variant { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { + Timber.i("No variants available for pre-Nougat devices") + return DEFAULT_VARIANT + } + val randomizedIndex = indexRandomizer.random(activeVariants) return activeVariants[randomizedIndex] diff --git a/app/src/main/res/drawable-hdpi/ic_default_browser_home_screen_button.png b/app/src/main/res/drawable-hdpi/ic_default_browser_home_screen_button.png new file mode 100644 index 000000000000..e0ca6942054f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_default_browser_home_screen_button.png differ diff --git a/app/src/main/res/drawable-hdpi/icon_default_browser_illustration.png b/app/src/main/res/drawable-hdpi/icon_default_browser_illustration.png index 572a37e9422c..11b73776627e 100644 Binary files a/app/src/main/res/drawable-hdpi/icon_default_browser_illustration.png and b/app/src/main/res/drawable-hdpi/icon_default_browser_illustration.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_default_browser_home_screen_button.png b/app/src/main/res/drawable-mdpi/ic_default_browser_home_screen_button.png new file mode 100644 index 000000000000..a58381c02da0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_default_browser_home_screen_button.png differ diff --git a/app/src/main/res/drawable-mdpi/icon_default_browser_illustration.png b/app/src/main/res/drawable-mdpi/icon_default_browser_illustration.png index 398824a6dd23..3dcbb64411f0 100644 Binary files a/app/src/main/res/drawable-mdpi/icon_default_browser_illustration.png and b/app/src/main/res/drawable-mdpi/icon_default_browser_illustration.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_default_browser_home_screen_button.png b/app/src/main/res/drawable-xhdpi/ic_default_browser_home_screen_button.png new file mode 100644 index 000000000000..141e55d48628 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_default_browser_home_screen_button.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_default_browser_illustration.png b/app/src/main/res/drawable-xhdpi/icon_default_browser_illustration.png index 95cbfe7c86e9..ace31a23d8ac 100644 Binary files a/app/src/main/res/drawable-xhdpi/icon_default_browser_illustration.png and b/app/src/main/res/drawable-xhdpi/icon_default_browser_illustration.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_default_browser_home_screen_button.png b/app/src/main/res/drawable-xxhdpi/ic_default_browser_home_screen_button.png new file mode 100644 index 000000000000..4b0997536f54 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_default_browser_home_screen_button.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_default_browser_illustration.png b/app/src/main/res/drawable-xxhdpi/icon_default_browser_illustration.png index 49271d5ed5f9..ce3e3aeaa847 100644 Binary files a/app/src/main/res/drawable-xxhdpi/icon_default_browser_illustration.png and b/app/src/main/res/drawable-xxhdpi/icon_default_browser_illustration.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_default_browser_home_screen_button.png b/app/src/main/res/drawable-xxxhdpi/ic_default_browser_home_screen_button.png new file mode 100644 index 000000000000..4ae1078b53d8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_default_browser_home_screen_button.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/icon_default_browser_illustration.png b/app/src/main/res/drawable-xxxhdpi/icon_default_browser_illustration.png index 2089605aebb1..6cdb78ec1fc6 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/icon_default_browser_illustration.png and b/app/src/main/res/drawable-xxxhdpi/icon_default_browser_illustration.png differ diff --git a/app/src/main/res/drawable/new_tab_set_default_browser_button_background.xml b/app/src/main/res/drawable/new_tab_set_default_browser_button_background.xml new file mode 100644 index 000000000000..fec268e1edff --- /dev/null +++ b/app/src/main/res/drawable/new_tab_set_default_browser_button_background.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ripple_light.xml b/app/src/main/res/drawable/ripple_light.xml new file mode 100644 index 000000000000..303b3b206844 --- /dev/null +++ b/app/src/main/res/drawable/ripple_light.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_default_browser_info.xml b/app/src/main/res/layout-land/activity_default_browser_info.xml index 85bc3b955630..d119b3191bac 100644 --- a/app/src/main/res/layout-land/activity_default_browser_info.xml +++ b/app/src/main/res/layout-land/activity_default_browser_info.xml @@ -79,11 +79,12 @@ android:id="@+id/defaultBrowserIllustration" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginEnd="20dp" android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" android:elevation="6dp" android:importantForAccessibility="no" android:src="@drawable/icon_default_browser_illustration" + android:transitionName="defaultBrowserCallToActionTransition" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/textEndVerticalGuideline" diff --git a/app/src/main/res/layout/activity_default_browser_info.xml b/app/src/main/res/layout/activity_default_browser_info.xml index 18b23f71b812..f6fefdcd6a17 100644 --- a/app/src/main/res/layout/activity_default_browser_info.xml +++ b/app/src/main/res/layout/activity_default_browser_info.xml @@ -68,9 +68,9 @@ android:layout_height="wrap_content" android:layout_marginTop="6dp" android:elevation="@dimen/modalCardHeaderElevation" - android:paddingBottom="30dp" - android:paddingEnd="20dp" android:paddingStart="20dp" + android:paddingEnd="20dp" + android:paddingBottom="30dp" android:text="@string/defaultBrowserInfoMessage" android:textAlignment="center" android:textColor="@color/white" @@ -81,9 +81,9 @@ android:id="@+id/dismissButton" android:layout_width="36dp" android:layout_height="36dp" - android:layout_marginEnd="4dp" android:layout_marginStart="14dp" android:layout_marginTop="4dp" + android:layout_marginEnd="4dp" android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/dismissDefaultBrowserInfo" android:elevation="7dp" @@ -97,10 +97,11 @@ android:id="@+id/defaultBrowserIllustration" android:layout_width="320dp" android:layout_height="0dp" - android:layout_marginBottom="20dp" android:layout_marginTop="20dp" + android:layout_marginBottom="20dp" android:importantForAccessibility="no" android:src="@drawable/icon_default_browser_illustration" + android:transitionName="defaultBrowserCallToActionTransition" app:layout_constraintBottom_toTopOf="@id/launchSettingsButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -110,9 +111,9 @@ android:id="@+id/launchSettingsButton" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginBottom="22dp" - android:layout_marginEnd="20dp" android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" + android:layout_marginBottom="22dp" android:background="@drawable/modal_card_button_bg" android:text="@string/onboardingDefaultBrowserGoToSettingsAction" android:textColor="@color/white" diff --git a/app/src/main/res/layout/fragment_browser_tab.xml b/app/src/main/res/layout/fragment_browser_tab.xml index 5c1605486863..75e0038c1465 100644 --- a/app/src/main/res/layout/fragment_browser_tab.xml +++ b/app/src/main/res/layout/fragment_browser_tab.xml @@ -20,7 +20,6 @@ android:id="@+id/rootView" android:layout_width="match_parent" android:layout_height="match_parent" - android:animateLayoutChanges="true" app:layout_scrollFlags="scroll|enterAlways"> @@ -37,28 +36,31 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + - - + tools:menu="@menu/menu_browser_activity"> - - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_home_screen_default_browser_call_to_action.xml b/app/src/main/res/layout/include_home_screen_default_browser_call_to_action.xml new file mode 100644 index 000000000000..407920ae84cd --- /dev/null +++ b/app/src/main/res/layout/include_home_screen_default_browser_call_to_action.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_new_browser_tab.xml b/app/src/main/res/layout/include_new_browser_tab.xml new file mode 100644 index 000000000000..b2ba10664e79 --- /dev/null +++ b/app/src/main/res/layout/include_new_browser_tab.xml @@ -0,0 +1,44 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index c4311b2febd7..905d59e032fa 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -32,4 +32,7 @@ 32dp 48dp + 20sp + 0.5 + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 3d2c319c5246..41234f64a5d4 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -37,5 +37,8 @@ 16dp 16dp + 15sp + 0.9 + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 0b0af342949f..98386d4a4592 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.2.50' + ext.kotlin_version = '1.2.51' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.0-beta01' + classpath 'com.android.tools.build:gradle:3.2.0-beta03' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong