Skip to content

Commit f549f8d

Browse files
committed
Merge branch 'release/5.27.0'
2 parents 0627c60 + cf2fdbc commit f549f8d

File tree

14 files changed

+152
-66
lines changed

14 files changed

+152
-66
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserChromeClientTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,10 @@ class BrowserChromeClientTest {
7878

7979
@UiThreadTest
8080
@Test
81-
fun whenOnProgressChangedCalledThenListenerInstructedToUpdateProgress() {
81+
fun whenOnProgressChangedCalledThenListenerInstructedToUpdateProgressAndNavigationOptions() {
8282
testee.onProgressChanged(webView, 10)
8383
verify(mockWebViewClientListener).progressChanged(webView.stubUrl, 10)
84+
verify(mockWebViewClientListener).navigationOptionsChanged(any())
8485
}
8586

8687
@UiThreadTest

app/src/androidTest/java/com/duckduckgo/app/notification/NotificationSchedulerTest.kt

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,19 @@ package com.duckduckgo.app.notification
2121

2222
import androidx.work.WorkInfo
2323
import androidx.work.WorkManager
24+
import com.duckduckgo.app.notification.NotificationScheduler.ClearDataNotificationWorker
25+
import com.duckduckgo.app.notification.NotificationScheduler.PrivacyNotificationWorker
2426
import com.duckduckgo.app.notification.model.SchedulableNotification
2527
import com.duckduckgo.app.statistics.VariantManager
2628
import com.duckduckgo.app.statistics.VariantManager.Companion.DEFAULT_VARIANT
2729
import com.nhaarman.mockitokotlin2.any
2830
import com.nhaarman.mockitokotlin2.mock
2931
import com.nhaarman.mockitokotlin2.whenever
3032
import kotlinx.coroutines.runBlocking
31-
import org.junit.Assert.assertFalse
3233
import org.junit.Assert.assertTrue
3334
import org.junit.Before
3435
import org.junit.Test
36+
import kotlin.reflect.jvm.jvmName
3537

3638
class NotificationSchedulerTest {
3739

@@ -45,31 +47,56 @@ class NotificationSchedulerTest {
4547
fun before() {
4648
whenever(variantManager.getVariant(any())).thenReturn(DEFAULT_VARIANT)
4749
testee = NotificationScheduler(
48-
variantManager,
4950
clearNotification,
5051
privacyNotification
5152
)
5253
}
5354

5455
@Test
55-
fun whenClearNotificationCanShowThenNotificationScheduled() = runBlocking<Unit> {
56+
fun whenBothPrivacyNotificationAndCleatDataCanShowThenPrivacyNotificationScheduled() = runBlocking<Unit> {
57+
whenever(privacyNotification.canShow()).thenReturn(true)
5658
whenever(clearNotification.canShow()).thenReturn(true)
5759
testee.scheduleNextNotification()
58-
assertTrue(notificationScheduled())
60+
assertNotificationScheduled(PrivacyNotificationWorker::class.jvmName)
5961
}
6062

6163
@Test
62-
fun whenClearNotificationCannotShowThenNotificationNotScheduled() = runBlocking<Unit> {
64+
fun whenPrivacyNotificationCanShowAndCleatDataCannotThenPrivacyNotificationScheduled() = runBlocking<Unit> {
65+
whenever(privacyNotification.canShow()).thenReturn(true)
6366
whenever(clearNotification.canShow()).thenReturn(false)
6467
testee.scheduleNextNotification()
65-
assertFalse(notificationScheduled())
68+
assertNotificationScheduled(PrivacyNotificationWorker::class.jvmName)
6669
}
6770

68-
private fun notificationScheduled(): Boolean {
71+
@Test
72+
fun whenPrivacyNotificationCannotShowAndClearNotificationCanShowThenNotificationScheduled() = runBlocking<Unit> {
73+
whenever(privacyNotification.canShow()).thenReturn(false)
74+
whenever(clearNotification.canShow()).thenReturn(true)
75+
testee.scheduleNextNotification()
76+
assertNotificationScheduled(ClearDataNotificationWorker::class.jvmName)
77+
}
78+
79+
@Test
80+
fun whenNoNotificationCanShowThenNoNotificationScheduled() = runBlocking<Unit> {
81+
whenever(privacyNotification.canShow()).thenReturn(false)
82+
whenever(clearNotification.canShow()).thenReturn(false)
83+
testee.scheduleNextNotification()
84+
assertNoNotificationScheduled()
85+
}
86+
87+
private fun assertNotificationScheduled(workerName: String) {
88+
assertTrue(getScheduledWorkers().any { it.tags.contains(workerName) })
89+
}
90+
91+
private fun assertNoNotificationScheduled() {
92+
assertTrue(getScheduledWorkers().isEmpty())
93+
}
94+
95+
private fun getScheduledWorkers(): List<WorkInfo> {
6996
return WorkManager
7097
.getInstance()
7198
.getWorkInfosByTag(NotificationScheduler.WORK_REQUEST_TAG)
7299
.get()
73-
.any { it.state == WorkInfo.State.ENQUEUED }
100+
.filter { it.state == WorkInfo.State.ENQUEUED }
74101
}
75102
}

app/src/androidTest/java/com/duckduckgo/app/statistics/VariantManagerTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ class VariantManagerTest {
2525
private val variants = VariantManager.ACTIVE_VARIANTS
2626

2727
@Test
28-
fun serpAndSharedControlVariantSuppressed() {
28+
fun serpAndSharedControlVariantActive() {
2929
val variant = variants.firstOrNull { it.key == "sc" }
30-
assertEqualsDouble(0.0, variant!!.weight)
30+
assertEqualsDouble(1.0, variant!!.weight)
3131
assertEquals(0, variant.features.size)
3232
}
3333

3434
@Test
35-
fun serpExperimentalVariantSuppressed() {
35+
fun serpExperimentalVariantActive() {
3636
val variant = variants.firstOrNull { it.key == "se" }
37-
assertEqualsDouble(0.0, variant!!.weight)
37+
assertEqualsDouble(1.0, variant!!.weight)
3838
assertEquals(0, variant.features.size)
3939
}
4040

app/src/main/java/com/duckduckgo/app/browser/BrowserChromeClient.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.view.View
2121
import android.webkit.ValueCallback
2222
import android.webkit.WebChromeClient
2323
import android.webkit.WebView
24+
import com.duckduckgo.app.browser.BrowserWebViewClient.*
2425
import kotlinx.coroutines.CoroutineScope
2526
import kotlinx.coroutines.Dispatchers
2627
import kotlinx.coroutines.SupervisorJob
@@ -61,6 +62,7 @@ class BrowserChromeClient @Inject constructor() : WebChromeClient(), CoroutineSc
6162
override fun onProgressChanged(webView: WebView, newProgress: Int) {
6263
Timber.d("onProgressChanged - $newProgress - ${webView.url}")
6364
webViewClientListener?.progressChanged(webView.url, newProgress)
65+
webViewClientListener?.navigationOptionsChanged(WebViewNavigationOptions(webView.copyBackForwardList()))
6466
}
6567

6668
override fun onReceivedTitle(view: WebView, title: String) {

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
198198
}
199199
}
200200

201-
private val logoHidingLayoutChangeListener by lazy { LogoHidingLayoutChangeListener(ddgLogo) }
201+
private val logoHidingListener by lazy { LogoHidingLayoutChangeLifecycleListener(ddgLogo) }
202202

203203
override fun onAttach(context: Context) {
204204
AndroidSupportInjection.inject(this)
@@ -243,6 +243,12 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
243243
addTextChangedListeners()
244244
appBarLayout.setExpanded(true)
245245
viewModel.onViewVisible()
246+
logoHidingListener.onResume()
247+
}
248+
249+
override fun onPause() {
250+
logoHidingListener.onPause()
251+
super.onPause()
246252
}
247253

248254
private fun createPopupMenu() {
@@ -326,12 +332,12 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
326332
}
327333

328334
private fun showHome() {
335+
showKeyboardImmediately()
329336
appBarLayout.setExpanded(true)
330-
logoHidingLayoutChangeListener.callToActionView = ctaContainer
331337
webView?.onPause()
332338
webView?.hide()
333339
omnibarScrolling.disableOmnibarScrolling(toolbarContainer)
334-
showKeyboard()
340+
logoHidingListener.onReadyToShowLogo()
335341
}
336342

337343
private fun showBrowser() {
@@ -607,11 +613,13 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
607613
}
608614

609615
private fun configureKeyboardAwareLogoAnimation() {
610-
// 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)
611-
newTabLayout.layoutTransition?.enableTransitionType(CHANGING)
612-
newTabLayout.layoutTransition?.disableTransitionType(DISAPPEARING)
613-
614-
rootView.addOnLayoutChangeListener(logoHidingLayoutChangeListener)
616+
newTabLayout.layoutTransition?.apply {
617+
// 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)
618+
enableTransitionType(CHANGING)
619+
disableTransitionType(DISAPPEARING)
620+
setDuration(LAYOUT_TRANSITION_MS)
621+
}
622+
rootView.addOnLayoutChangeListener(logoHidingListener)
615623
}
616624

617625
private fun userEnteredQuery(query: String) {
@@ -659,6 +667,10 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
659667

660668
it.setFindListener(this)
661669
}
670+
671+
if (BuildConfig.DEBUG) {
672+
WebView.setWebContentsDebuggingEnabled(true)
673+
}
662674
}
663675

664676
/**
@@ -761,6 +773,13 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
761773
}
762774
}
763775

776+
private fun showKeyboardImmediately() {
777+
if (!isHidden) {
778+
Timber.v("Keyboard now showing")
779+
omnibarTextInput?.showKeyboard()
780+
}
781+
}
782+
764783
private fun showKeyboard() {
765784
if (!isHidden) {
766785
Timber.v("Keyboard now showing")
@@ -933,6 +952,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
933952

934953
private const val ADD_BOOKMARK_FRAGMENT_TAG = "ADD_BOOKMARK"
935954
private const val KEYBOARD_DELAY = 200L
955+
private const val LAYOUT_TRANSITION_MS = 200L
936956

937957
private const val REQUEST_CODE_CHOOSE_FILE = 100
938958
private const val PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 200
@@ -1128,7 +1148,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
11281148
ctaContainer.removeAllViews()
11291149

11301150
inflate(context, R.layout.include_cta, ctaContainer)
1131-
logoHidingLayoutChangeListener.callToActionView = ctaContainer
1151+
logoHidingListener.callToActionView = ctaContainer
11321152

11331153
configuration.apply(ctaContainer)
11341154
ctaContainer.ctaOkButton.setOnClickListener {

app/src/main/java/com/duckduckgo/app/browser/LogoHidingLayoutChangeListener.kt renamed to app/src/main/java/com/duckduckgo/app/browser/LogoHidingLayoutChangeLifecycleListener.kt

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,66 @@ package com.duckduckgo.app.browser
1818

1919
import android.graphics.Rect
2020
import android.view.View
21+
import android.view.animation.AccelerateInterpolator
2122
import androidx.core.view.isGone
2223
import androidx.core.view.isVisible
2324
import com.duckduckgo.app.global.view.toDp
2425
import timber.log.Timber
2526

2627

27-
class LogoHidingLayoutChangeListener(private var ddgLogoView: View) : View.OnLayoutChangeListener {
28+
class LogoHidingLayoutChangeLifecycleListener(private var ddgLogoView: View) : View.OnLayoutChangeListener {
2829

2930
var callToActionView: View? = null
3031

32+
private var readyToShowLogo = false
33+
34+
fun onReadyToShowLogo() {
35+
readyToShowLogo = true
36+
update()
37+
}
38+
3139
override fun onLayoutChange(view: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
3240
update()
3341
}
3442

35-
fun update() {
36-
val heightDp = getHeightDp()
43+
fun onResume() {
44+
update()
45+
}
3746

38-
Timber.v("App height now: $heightDp dp, call to action button showing: ${callToActionView?.isVisible}")
47+
fun onPause() {
48+
hideLogo()
49+
}
3950

40-
if (enoughRoomForLogo(heightDp)) {
41-
ddgLogoView.alpha = 1.0f
51+
private fun update() {
52+
val heightDp = getHeightDp()
53+
Timber.v("App height now: $heightDp dp, call to action button showing: ${callToActionView?.isVisible}")
54+
if (readyToShowLogo && enoughRoomForLogo(heightDp)) {
55+
fadeLogoIn()
4256
} else {
43-
ddgLogoView.alpha = 0f
57+
fadeLogoOut()
58+
}
59+
}
60+
61+
private fun fadeLogoIn() {
62+
ddgLogoView.animate().apply {
63+
duration = FADE_IN_DURATION
64+
interpolator = AccelerateInterpolator()
65+
alpha(1f)
66+
}
67+
}
68+
69+
private fun fadeLogoOut() {
70+
ddgLogoView.animate().apply {
71+
duration = FADE_OUT_DURATION
72+
interpolator = AccelerateInterpolator()
73+
alpha(0f)
4474
}
4575
}
4676

77+
private fun hideLogo() {
78+
ddgLogoView.alpha = 0f
79+
}
80+
4781
private fun getHeightDp(): Int {
4882
val r = Rect()
4983
ddgLogoView.getWindowVisibleDisplayFrame(r)
@@ -52,11 +86,10 @@ class LogoHidingLayoutChangeListener(private var ddgLogoView: View) : View.OnLay
5286

5387
private fun enoughRoomForLogo(heightDp: Int): Boolean {
5488

55-
val isGone = callToActionView?.isGone ?: true
56-
if (isGone) {
89+
val isCallToActionHidden = callToActionView?.isGone ?: true
90+
if (isCallToActionHidden) {
5791
return true
5892
}
59-
6093
val callToActionButtonHeightDp = callToActionView?.measuredHeight?.toDp() ?: 0
6194
val heightMinusCallToAction = heightDp - callToActionButtonHeightDp
6295
if (heightMinusCallToAction >= MINIMUM_AVAILABLE_HEIGHT_REQUIRED_TO_SHOW_LOGO) {
@@ -68,6 +101,8 @@ class LogoHidingLayoutChangeListener(private var ddgLogoView: View) : View.OnLay
68101

69102
companion object {
70103
private const val MINIMUM_AVAILABLE_HEIGHT_REQUIRED_TO_SHOW_LOGO = 220
104+
private const val FADE_IN_DURATION = 1000L
105+
private const val FADE_OUT_DURATION = 100L
71106
}
72107

73108
}

app/src/main/java/com/duckduckgo/app/di/NotificationModule.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import com.duckduckgo.app.notification.model.ClearDataNotification
2727
import com.duckduckgo.app.notification.model.PrivacyProtectionNotification
2828
import com.duckduckgo.app.privacy.db.PrivacyProtectionCountDao
2929
import com.duckduckgo.app.settings.db.SettingsDataStore
30-
import com.duckduckgo.app.statistics.VariantManager
3130
import dagger.Module
3231
import dagger.Provides
3332
import javax.inject.Singleton
@@ -74,12 +73,10 @@ class NotificationModule {
7473
@Provides
7574
@Singleton
7675
fun providesNotificationScheduler(
77-
variantManager: VariantManager,
7876
clearDataNotification: ClearDataNotification,
7977
privacyProtectionNotification: PrivacyProtectionNotification
8078
): NotificationScheduler {
8179
return NotificationScheduler(
82-
variantManager,
8380
clearDataNotification,
8481
privacyProtectionNotification
8582
)

app/src/main/java/com/duckduckgo/app/notification/NotificationScheduler.kt

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,25 @@ import com.duckduckgo.app.notification.db.NotificationDao
2828
import com.duckduckgo.app.notification.model.Notification
2929
import com.duckduckgo.app.notification.model.NotificationSpec
3030
import com.duckduckgo.app.notification.model.SchedulableNotification
31-
import com.duckduckgo.app.statistics.VariantManager
32-
import com.duckduckgo.app.statistics.VariantManager.VariantFeature.*
3331
import com.duckduckgo.app.statistics.pixels.Pixel
3432
import com.duckduckgo.app.statistics.pixels.Pixel.PixelName.NOTIFICATION_SHOWN
3533
import timber.log.Timber
3634
import java.util.concurrent.TimeUnit
3735
import javax.inject.Inject
3836

3937
class NotificationScheduler @Inject constructor(
40-
private val variantManager: VariantManager,
4138
private val clearDataNotification: SchedulableNotification,
4239
private val privacyNotification: SchedulableNotification
4340
) {
4441
suspend fun scheduleNextNotification() {
4542

4643
WorkManager.getInstance().cancelAllWorkByTag(WORK_REQUEST_TAG)
47-
val variant = variantManager.getVariant()
4844

4945
when {
50-
variant.hasFeature(NotificationPrivacyDay1) && privacyNotification.canShow() -> {
46+
privacyNotification.canShow() -> {
5147
scheduleNotification(OneTimeWorkRequestBuilder<PrivacyNotificationWorker>(), 1, TimeUnit.DAYS)
5248
}
53-
variant.hasFeature(NotificationClearDataDay1) && clearDataNotification.canShow() -> {
54-
scheduleNotification(OneTimeWorkRequestBuilder<ClearDataNotificationWorker>(), 1, TimeUnit.DAYS)
55-
}
56-
!variant.hasFeature(NotificationSuppressClearDataDay3) && clearDataNotification.canShow() -> {
49+
clearDataNotification.canShow() -> {
5750
scheduleNotification(OneTimeWorkRequestBuilder<ClearDataNotificationWorker>(), 3, TimeUnit.DAYS)
5851
}
5952
else -> Timber.v("Notifications not enabled for this variant")
@@ -70,9 +63,9 @@ class NotificationScheduler @Inject constructor(
7063
WorkManager.getInstance().enqueue(request)
7164
}
7265

73-
// Legacy code. Unused class required for users who already have this notification scheduled from previous version. We can delete this in a
74-
// couple of weeks once old notifications have cleared. We should also remove "open" access from ClearDataNotificationWorker.
75-
class ShowClearDataNotification(context: Context, params: WorkerParameters): ClearDataNotificationWorker(context, params)
66+
// Legacy code. Unused class required for users who already have this notification scheduled from previous version. We will
67+
// delete this as part of https://app.asana.com/0/414730916066338/1119619712088571
68+
class ShowClearDataNotification(context: Context, params: WorkerParameters) : ClearDataNotificationWorker(context, params)
7669

7770
open class ClearDataNotificationWorker(context: Context, params: WorkerParameters) : SchedulableNotificationWorker(context, params)
7871
class PrivacyNotificationWorker(context: Context, params: WorkerParameters) : SchedulableNotificationWorker(context, params)

0 commit comments

Comments
 (0)