Skip to content

Commit 384e6a2

Browse files
authored
Adjust VPN menu item dismiss behavior (#6854)
Task/Issue URL: https://app.asana.com/1/137249556945/task/1211443366455926?focus=true ### Description Adjust VPN menu item dismiss behavior ### Steps to test this PR _Pre-steps_ - [x] Apply PPro patch -> https://app.asana.com/1/137249556945/project/1209991789468715/task/1210448620621729?focus=true - [x] Set device language to English (United Kingdom) _Not Subscribed_ - [x] Fresh install - [x] Open a new tab page - [x] Check you see the new VPN menu item with pill "TRY FOR FREE" _Frequency cap_ - [x] Tap on VPN menu item - [x] Open a new tab - [x] Open overflow menu at least 3 more times - [x] Open a new tab - [x] Open overflow menu - [x] Check VPN menu item doesn't have the yellow 'TRY FOR FREE' pill anymore - [x] Tab on VPN item - [x] Check Subscription paywall is opened with origin "funnel_appmenu_android" (you can check this in the logcat filtering by "origin" _no UK_ - [x] Set device country to something different to GB - [x] Open overflow menu in a new tab page - [x] Check VPN menu item is not there ### UI changes | Before | After | | ------ | ----- | <img width="377" height="341" alt="Screenshot 2025-09-26 at 12 21 21" src="https://github.com/user-attachments/assets/65f4acc1-c066-4a99-bda6-fe4959fbc6c1" />|<img width="379" height="386" alt="Screenshot 2025-09-26 at 12 16 23" src="https://github.com/user-attachments/assets/d58580f9-7ed6-4184-908d-67aa439459e9" />|
1 parent 9a6b40b commit 384e6a2

File tree

7 files changed

+376
-315
lines changed

7 files changed

+376
-315
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4416,6 +4416,9 @@ class BrowserTabViewModel @Inject constructor(
44164416
VpnMenuState.NotSubscribed -> {
44174417
command.value = LaunchPrivacyPro("https://duckduckgo.com/pro?origin=funnel_appmenu_android".toUri())
44184418
}
4419+
VpnMenuState.NotSubscribedNoPill -> {
4420+
command.value = LaunchPrivacyPro("https://duckduckgo.com/pro?origin=funnel_appmenu_android".toUri())
4421+
}
44194422
is VpnMenuState.Subscribed -> {
44204423
command.value = LaunchVpnManagement
44214424
}

app/src/main/java/com/duckduckgo/app/browser/menu/BrowserPopupMenu.kt

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.duckduckgo.app.browser.viewstate.BrowserViewState
3030
import com.duckduckgo.app.browser.viewstate.VpnMenuState
3131
import com.duckduckgo.common.ui.menu.PopupMenu
3232
import com.duckduckgo.common.ui.view.MenuItemView
33+
import com.duckduckgo.common.ui.view.StatusIndicatorView
3334
import com.duckduckgo.mobile.android.R.drawable
3435

3536
class BrowserPopupMenu(
@@ -45,10 +46,11 @@ class BrowserPopupMenu(
4546
private val bottomBinding = PopupWindowBrowserMenuBottomBinding.bind(contentView)
4647

4748
init {
48-
contentView = when (popupMenuResourceType) {
49-
TOP -> topBinding.root
50-
BOTTOM -> bottomBinding.root
51-
}
49+
contentView =
50+
when (popupMenuResourceType) {
51+
TOP -> topBinding.root
52+
BOTTOM -> bottomBinding.root
53+
}
5254
}
5355

5456
internal val backMenuItem: View by lazy {
@@ -310,13 +312,14 @@ class BrowserPopupMenu(
310312
addToHomeMenuItem.isVisible = viewState.addToHomeVisible && viewState.addToHomeEnabled && !displayedInCustomTabScreen
311313
privacyProtectionMenuItem.isVisible = viewState.canChangePrivacyProtection
312314
privacyProtectionMenuItem.label {
313-
context.getText(
314-
if (viewState.isPrivacyProtectionDisabled) {
315-
R.string.enablePrivacyProtection
316-
} else {
317-
R.string.disablePrivacyProtection
318-
},
319-
).toString()
315+
context
316+
.getText(
317+
if (viewState.isPrivacyProtectionDisabled) {
318+
R.string.enablePrivacyProtection
319+
} else {
320+
R.string.disablePrivacyProtection
321+
},
322+
).toString()
320323
}
321324
privacyProtectionMenuItem.setIcon(
322325
if (viewState.isPrivacyProtectionDisabled) drawable.ic_shield_16 else drawable.ic_shield_disabled_16,
@@ -360,51 +363,69 @@ class BrowserPopupMenu(
360363
VpnMenuState.NotSubscribed -> {
361364
vpnMenuItem.isVisible = shouldShowVpnMenuItem
362365
if (shouldShowVpnMenuItem) {
363-
configureVpnMenuItemForNotSubscribed()
366+
val (tryForFreePill, statusIndicator, menuItemView) = getVpnMenuViews()
367+
configureVpnMenuItemForNotSubscribed(tryForFreePill, statusIndicator, menuItemView)
368+
}
369+
}
370+
VpnMenuState.NotSubscribedNoPill -> {
371+
vpnMenuItem.isVisible = shouldShowVpnMenuItem
372+
if (shouldShowVpnMenuItem) {
373+
val (tryForFreePill, statusIndicator, menuItemView) = getVpnMenuViews()
374+
configureVpnMenuItemForNotSubscribedNoPill(tryForFreePill, statusIndicator, menuItemView)
364375
}
365376
}
366377
is VpnMenuState.Subscribed -> {
367378
vpnMenuItem.isVisible = shouldShowVpnMenuItem
368379
if (shouldShowVpnMenuItem) {
369-
configureVpnMenuItemForSubscribed(viewState.vpnMenuState.isVpnEnabled)
380+
val (tryForFreePill, statusIndicator, menuItemView) = getVpnMenuViews()
381+
configureVpnMenuItemForSubscribed(tryForFreePill, statusIndicator, menuItemView, viewState.vpnMenuState.isVpnEnabled)
370382
}
371383
}
372384
}
373385
}
374386

375-
private fun configureVpnMenuItemForNotSubscribed() {
376-
val tryForFreePill = when (popupMenuResourceType) {
377-
TOP -> topBinding.includeVpnMenuItem.tryForFreePill
378-
BOTTOM -> bottomBinding.includeVpnMenuItem.tryForFreePill
379-
}
380-
val statusIndicator = when (popupMenuResourceType) {
381-
TOP -> topBinding.includeVpnMenuItem.statusIndicator
382-
BOTTOM -> bottomBinding.includeVpnMenuItem.statusIndicator
383-
}
384-
val menuItemView = when (popupMenuResourceType) {
385-
TOP -> topBinding.includeVpnMenuItem.menuItemView
386-
BOTTOM -> bottomBinding.includeVpnMenuItem.menuItemView
387-
}
388-
387+
private fun getVpnMenuViews() =
388+
when (popupMenuResourceType) {
389+
TOP ->
390+
Triple(
391+
topBinding.includeVpnMenuItem.tryForFreePill,
392+
topBinding.includeVpnMenuItem.statusIndicator,
393+
topBinding.includeVpnMenuItem.menuItemView,
394+
)
395+
BOTTOM ->
396+
Triple(
397+
bottomBinding.includeVpnMenuItem.tryForFreePill,
398+
bottomBinding.includeVpnMenuItem.statusIndicator,
399+
bottomBinding.includeVpnMenuItem.menuItemView,
400+
)
401+
}
402+
403+
private fun configureVpnMenuItemForNotSubscribed(
404+
tryForFreePill: View,
405+
statusIndicator: StatusIndicatorView,
406+
menuItemView: MenuItemView,
407+
) {
389408
tryForFreePill.isVisible = true
390409
statusIndicator.isVisible = false
391410
menuItemView.setIcon(drawable.ic_vpn_unlocked_24)
392411
}
393412

394-
private fun configureVpnMenuItemForSubscribed(isVpnEnabled: Boolean) {
395-
val tryForFreePill = when (popupMenuResourceType) {
396-
TOP -> topBinding.includeVpnMenuItem.tryForFreePill
397-
BOTTOM -> bottomBinding.includeVpnMenuItem.tryForFreePill
398-
}
399-
val statusIndicator = when (popupMenuResourceType) {
400-
TOP -> topBinding.includeVpnMenuItem.statusIndicator
401-
BOTTOM -> bottomBinding.includeVpnMenuItem.statusIndicator
402-
}
403-
val menuItemView = when (popupMenuResourceType) {
404-
TOP -> topBinding.includeVpnMenuItem.menuItemView
405-
BOTTOM -> bottomBinding.includeVpnMenuItem.menuItemView
406-
}
413+
private fun configureVpnMenuItemForNotSubscribedNoPill(
414+
tryForFreePill: View,
415+
statusIndicator: StatusIndicatorView,
416+
menuItemView: MenuItemView,
417+
) {
418+
tryForFreePill.isVisible = false
419+
statusIndicator.isVisible = false
420+
menuItemView.setIcon(drawable.ic_vpn_unlocked_24)
421+
}
407422

423+
private fun configureVpnMenuItemForSubscribed(
424+
tryForFreePill: View,
425+
statusIndicator: StatusIndicatorView,
426+
menuItemView: MenuItemView,
427+
isVpnEnabled: Boolean,
428+
) {
408429
tryForFreePill.isVisible = false
409430
statusIndicator.isVisible = true
410431
statusIndicator.setStatus(isVpnEnabled)

app/src/main/java/com/duckduckgo/app/browser/menu/VpnMenuStateProvider.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class VpnMenuStateProviderImpl @Inject constructor(
3939
private val androidBrowserConfigFeature: AndroidBrowserConfigFeature,
4040
private val vpnMenuStore: VpnMenuStore,
4141
) : VpnMenuStateProvider {
42-
4342
override fun getVpnMenuState(): Flow<VpnMenuState> {
4443
return combine(
4544
subscriptions.getSubscriptionStatusFlow(),
@@ -60,20 +59,19 @@ class VpnMenuStateProviderImpl @Inject constructor(
6059
if (vpnMenuStore.canShowVpnMenuForNotSubscribed()) {
6160
VpnMenuState.NotSubscribed
6261
} else {
63-
VpnMenuState.Hidden
62+
VpnMenuState.NotSubscribedNoPill
6463
}
6564
}
6665
}
6766
}
6867
}
6968
}
7069

71-
private fun SubscriptionStatus.isActive(): Boolean {
72-
return when (this) {
70+
private fun SubscriptionStatus.isActive(): Boolean =
71+
when (this) {
7372
SubscriptionStatus.AUTO_RENEWABLE,
7473
SubscriptionStatus.NOT_AUTO_RENEWABLE,
7574
SubscriptionStatus.GRACE_PERIOD,
7675
-> true
7776
else -> false
7877
}
79-
}

app/src/main/java/com/duckduckgo/app/browser/viewstate/BrowserViewState.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,14 @@ data class BrowserViewState(
6565

6666
sealed class VpnMenuState {
6767
data object Hidden : VpnMenuState()
68+
6869
data object NotSubscribed : VpnMenuState()
69-
data class Subscribed(val isVpnEnabled: Boolean) : VpnMenuState()
70+
71+
data object NotSubscribedNoPill : VpnMenuState()
72+
73+
data class Subscribed(
74+
val isVpnEnabled: Boolean,
75+
) : VpnMenuState()
7076
}
7177

7278
sealed class HighlightableButton {
@@ -77,17 +83,15 @@ sealed class HighlightableButton {
7783

7884
data object Gone : HighlightableButton()
7985

80-
fun isHighlighted(): Boolean {
81-
return when (this) {
86+
fun isHighlighted(): Boolean =
87+
when (this) {
8288
is Visible -> this.highlighted
8389
is Gone -> false
8490
}
85-
}
8691

87-
fun isEnabled(): Boolean {
88-
return when (this) {
92+
fun isEnabled(): Boolean =
93+
when (this) {
8994
is Visible -> this.enabled
9095
is Gone -> false
9196
}
92-
}
9397
}

0 commit comments

Comments
 (0)