Skip to content

Commit 91899c6

Browse files
authored
VPN menu item in New Tab Page (#6832)
Task/Issue URL: https://app.asana.com/1/137249556945/task/1210561963620629?focus=true ### Description Add VPN menu item in New Tab page for UK users ### Steps to test this PR _Pre-steps_ - [x] Apply PPro patch -> https://app.asana.com/1/137249556945/project/1209991789468715/task/1210448620621729?focus=true - [x] Change remote config URL to https://jsonblob.com/api/1419726762371375104 - [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" _Subscribed & Active_ - [x] Tap on VPN menu item - [x] Subscription paywall is opened with origin "funnel_appmenu_android" (you can check this in the logcat filtering by "origin" - [x] Purchase a subscription - [x] Once the purchase is finished, go back to new tab page - [x] Open overflow menu - [x] Check VPN menu item is there with off indicator _Subscribed & Not Active_ - [x] Tap on VPN menu item - [x] Check it navigates to VPN management screen - [x] Enable VPN - [x] Go back to new tab page - [x] Open overflow menu - [x] Check VPN menu item is there with on indicator _Browser Menu_ - [x] Make a search - [x] Open overflow menu - [x] Check VPN menu item is not there - [x] Go to a website - [x] Check VPN menu item is not there either _Not en-GB_ - [x] Switch your device language to something other than English (United Kingdom) - [x] Go back to the browser - [x] Open a new tab - [x] Open overflow menu - [x] Check VPN is not there, as user it is not eligible _FF disabled_ - [x] Go to https://jsonblob.com/1419726762371375104 and disable "vpnMenuItem" sub-feature - [x] Set device language back to English (United Kingdom) - [x] Go to a new tab page - [x] Check VPN menu item is not shown ### UI changes | Not Subscribed | Subscribed & Active | Subscribed & Not Active | | ------ | ----- | ----- | <img width="380" height="844" alt="vpn-menu-not-subscribed" src="https://github.com/user-attachments/assets/1339ce10-0414-42bb-9774-b67a3200bb39" />|<img width="374" height="840" alt="vpn-menu-subscribed-active" src="https://github.com/user-attachments/assets/691f6618-164e-4e21-906b-ed955758d32c" />|<img width="378" height="845" alt="vpn-menu-subscribed-not-active" src="https://github.com/user-attachments/assets/25ef2aba-277c-4c0c-8acd-c338afda4ec7" />|
1 parent 7adfe77 commit 91899c6

File tree

20 files changed

+1219
-0
lines changed

20 files changed

+1219
-0
lines changed

app-tracking-protection/vpn-impl/src/main/res/values/donottranslate.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@
3939

4040
<!-- New Tab -->
4141
<string name="vpn_NewTabSectionSettingsTitle">App Tracking Protection Status</string>
42+
<string name="vpnMenuTitle">VPN</string>
4243

4344
</resources>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ import com.duckduckgo.app.browser.logindetection.LoginDetected
107107
import com.duckduckgo.app.browser.logindetection.NavigationAwareLoginDetector
108108
import com.duckduckgo.app.browser.logindetection.NavigationEvent
109109
import com.duckduckgo.app.browser.logindetection.NavigationEvent.LoginAttempt
110+
import com.duckduckgo.app.browser.menu.VpnMenuStateProvider
110111
import com.duckduckgo.app.browser.model.BasicAuthenticationCredentials
111112
import com.duckduckgo.app.browser.model.BasicAuthenticationRequest
112113
import com.duckduckgo.app.browser.model.LongPressTarget
@@ -128,6 +129,7 @@ import com.duckduckgo.app.browser.viewstate.FindInPageViewState
128129
import com.duckduckgo.app.browser.viewstate.GlobalLayoutViewState
129130
import com.duckduckgo.app.browser.viewstate.HighlightableButton
130131
import com.duckduckgo.app.browser.viewstate.LoadingViewState
132+
import com.duckduckgo.app.browser.viewstate.VpnMenuState
131133
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.LearnMore
132134
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.LeaveSite
133135
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.ReportError
@@ -450,6 +452,8 @@ class BrowserTabViewModelTest {
450452

451453
private val mockExternalIntentProcessingState: ExternalIntentProcessingState = mock()
452454

455+
private val mockVpnMenuStateProvider: VpnMenuStateProvider = mock()
456+
453457
private val mockHasPendingTabLaunchFlow = MutableStateFlow(false)
454458

455459
private val mockHasPendingDuckAiOpenFlow = MutableStateFlow(false)
@@ -660,6 +664,7 @@ class BrowserTabViewModelTest {
660664
whenever(mockDuckAiFeatureState.showInputScreenAutomaticallyOnNewTab).thenReturn(mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow)
661665
whenever(mockExternalIntentProcessingState.hasPendingTabLaunch).thenReturn(mockHasPendingTabLaunchFlow)
662666
whenever(mockExternalIntentProcessingState.hasPendingDuckAiOpen).thenReturn(mockHasPendingDuckAiOpenFlow)
667+
whenever(mockVpnMenuStateProvider.getVpnMenuState()).thenReturn(flowOf(VpnMenuState.Hidden))
663668
whenever(mockOnboardingDesignExperimentManager.isModifiedControlEnrolledAndEnabled()).thenReturn(false)
664669
whenever(mockOnboardingDesignExperimentManager.isBuckEnrolledAndEnabled()).thenReturn(false)
665670
whenever(mockOnboardingDesignExperimentManager.isBbEnrolledAndEnabled()).thenReturn(false)
@@ -810,6 +815,7 @@ class BrowserTabViewModelTest {
810815
serpEasterEggLogosToggles = mockSerpEasterEggLogoToggles,
811816
nonHttpAppLinkChecker = nonHttpAppLinkChecker,
812817
externalIntentProcessingState = mockExternalIntentProcessingState,
818+
vpnMenuStateProvider = mockVpnMenuStateProvider,
813819
)
814820

815821
testee.loadData("abc", null, false, false)

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ import com.duckduckgo.app.browser.history.NavigationHistorySheet.NavigationHisto
138138
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
139139
import com.duckduckgo.app.browser.logindetection.DOMLoginDetector
140140
import com.duckduckgo.app.browser.menu.BrowserPopupMenu
141+
import com.duckduckgo.app.browser.menu.VpnMenuStore
141142
import com.duckduckgo.app.browser.model.BasicAuthenticationCredentials
142143
import com.duckduckgo.app.browser.model.BasicAuthenticationRequest
143144
import com.duckduckgo.app.browser.model.LongPressTarget
@@ -173,6 +174,7 @@ import com.duckduckgo.app.browser.viewstate.LoadingViewState
173174
import com.duckduckgo.app.browser.viewstate.OmnibarViewState
174175
import com.duckduckgo.app.browser.viewstate.PrivacyShieldViewState
175176
import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState
177+
import com.duckduckgo.app.browser.viewstate.VpnMenuState
176178
import com.duckduckgo.app.browser.webauthn.WebViewPasskeyInitializer
177179
import com.duckduckgo.app.browser.webshare.WebShareChooser
178180
import com.duckduckgo.app.browser.webshare.WebViewCompatWebShareChooser
@@ -307,6 +309,7 @@ import com.duckduckgo.mobile.android.R as CommonR
307309
import com.duckduckgo.mobile.android.app.tracking.ui.AppTrackingProtectionScreens.AppTrackerOnboardingActivityWithEmptyParamsParams
308310
import com.duckduckgo.navigation.api.GlobalActivityStarter
309311
import com.duckduckgo.navigation.api.GlobalActivityStarter.DeeplinkActivityParams
312+
import com.duckduckgo.networkprotection.api.NetworkProtectionScreens.NetworkProtectionManagementScreenNoParams
310313
import com.duckduckgo.privacy.dashboard.api.ui.DashboardOpener
311314
import com.duckduckgo.privacy.dashboard.api.ui.PrivacyDashboardHybridScreenParams.BrokenSiteForm
312315
import com.duckduckgo.privacy.dashboard.api.ui.PrivacyDashboardHybridScreenParams.BrokenSiteForm.BrokenSiteFormReportFlow
@@ -407,6 +410,9 @@ class BrowserTabFragment :
407410
@Inject
408411
lateinit var pixel: Pixel
409412

413+
@Inject
414+
lateinit var vpnMenuStore: VpnMenuStore
415+
410416
@Inject
411417
lateinit var ctaViewModel: CtaViewModel
412418

@@ -1346,6 +1352,10 @@ class BrowserTabFragment :
13461352
pixel.fire(CustomTabPixelNames.CUSTOM_TABS_OPEN_IN_DDG)
13471353
}
13481354
}
1355+
1356+
onMenuItemClicked(vpnMenuItem) {
1357+
viewModel.onVpnMenuClicked()
1358+
}
13491359
}
13501360
}
13511361

@@ -1376,6 +1386,15 @@ class BrowserTabFragment :
13761386
// small delay added to let keyboard disappear and avoid jarring transition
13771387
binding.rootView.postDelayed(POPUP_MENU_DELAY) {
13781388
if (isAdded) {
1389+
// Check if VPN menu item will be shown to non-subscribed user and increment count
1390+
val currentViewState = viewModel.browserViewState.value
1391+
if (currentViewState != null) {
1392+
val shouldShowVpnMenuItem = !currentViewState.browserShowing && !isActiveCustomTab()
1393+
if (currentViewState.vpnMenuState == VpnMenuState.NotSubscribed && shouldShowVpnMenuItem) {
1394+
vpnMenuStore.incrementVpnMenuShownCount()
1395+
}
1396+
}
1397+
13791398
popupMenu.show(binding.rootView, omnibar.toolbar)
13801399
viewModel.onPopupMenuLaunched()
13811400
if (isActiveCustomTab()) {
@@ -1993,6 +2012,10 @@ class BrowserTabFragment :
19932012
}
19942013
}
19952014

2015+
is Command.LaunchVpnManagement -> {
2016+
globalActivityStarter.start(requireContext(), NetworkProtectionManagementScreenNoParams)
2017+
}
2018+
19962019
is Command.DialNumber -> {
19972020
val intent = Intent(Intent.ACTION_DIAL)
19982021
intent.data = Uri.parse("tel:${it.telephoneNumber}")

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ import com.duckduckgo.app.browser.commands.Command.LaunchNewTab
111111
import com.duckduckgo.app.browser.commands.Command.LaunchPopupMenu
112112
import com.duckduckgo.app.browser.commands.Command.LaunchPrivacyPro
113113
import com.duckduckgo.app.browser.commands.Command.LaunchTabSwitcher
114+
import com.duckduckgo.app.browser.commands.Command.LaunchVpnManagement
114115
import com.duckduckgo.app.browser.commands.Command.LoadExtractedUrl
115116
import com.duckduckgo.app.browser.commands.Command.OpenAppLink
116117
import com.duckduckgo.app.browser.commands.Command.OpenBrokenSiteLearnMore
@@ -185,6 +186,7 @@ import com.duckduckgo.app.browser.logindetection.FireproofDialogsEventHandler.Ev
185186
import com.duckduckgo.app.browser.logindetection.LoginDetected
186187
import com.duckduckgo.app.browser.logindetection.NavigationAwareLoginDetector
187188
import com.duckduckgo.app.browser.logindetection.NavigationEvent
189+
import com.duckduckgo.app.browser.menu.VpnMenuStateProvider
188190
import com.duckduckgo.app.browser.model.BasicAuthenticationCredentials
189191
import com.duckduckgo.app.browser.model.BasicAuthenticationRequest
190192
import com.duckduckgo.app.browser.model.LongPressTarget
@@ -212,6 +214,7 @@ import com.duckduckgo.app.browser.viewstate.LoadingViewState
212214
import com.duckduckgo.app.browser.viewstate.OmnibarViewState
213215
import com.duckduckgo.app.browser.viewstate.PrivacyShieldViewState
214216
import com.duckduckgo.app.browser.viewstate.SavedSiteChangedViewState
217+
import com.duckduckgo.app.browser.viewstate.VpnMenuState
215218
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout
216219
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.LearnMore
217220
import com.duckduckgo.app.browser.webview.MaliciousSiteBlockedWarningLayout.Action.LeaveSite
@@ -480,6 +483,7 @@ class BrowserTabViewModel @Inject constructor(
480483
private val serpEasterEggLogosToggles: SerpEasterEggLogosToggles,
481484
private val nonHttpAppLinkChecker: NonHttpAppLinkChecker,
482485
private val externalIntentProcessingState: ExternalIntentProcessingState,
486+
private val vpnMenuStateProvider: VpnMenuStateProvider,
483487
) : WebViewClientListener,
484488
EditSavedSiteListener,
485489
DeleteBookmarkListener,
@@ -731,6 +735,13 @@ class BrowserTabViewModel @Inject constructor(
731735
.flowOn(dispatchers.main())
732736
.launchIn(viewModelScope)
733737

738+
vpnMenuStateProvider.getVpnMenuState()
739+
.onEach { vpnMenuState ->
740+
browserViewState.value = currentBrowserViewState().copy(vpnMenuState = vpnMenuState)
741+
}
742+
.flowOn(dispatchers.main())
743+
.launchIn(viewModelScope)
744+
734745
additionalDefaultBrowserPrompts.showSetAsDefaultPopupMenuItem
735746
.onEach {
736747
browserViewState.value = currentBrowserViewState().copy(showSelectDefaultBrowserMenuItem = it)
@@ -4335,6 +4346,19 @@ class BrowserTabViewModel @Inject constructor(
43354346
}
43364347
}
43374348

4349+
fun onVpnMenuClicked() {
4350+
val vpnMenuState = currentBrowserViewState().vpnMenuState
4351+
when (vpnMenuState) {
4352+
VpnMenuState.NotSubscribed -> {
4353+
command.value = LaunchPrivacyPro("https://duckduckgo.com/pro?origin=funnel_appmenu_android".toUri())
4354+
}
4355+
is VpnMenuState.Subscribed -> {
4356+
command.value = LaunchVpnManagement
4357+
}
4358+
VpnMenuState.Hidden -> {} // Should not happen as menu item should not be visible
4359+
}
4360+
}
4361+
43384362
fun onAutoConsentPopUpHandled(isCosmetic: Boolean) {
43394363
if (!currentBrowserViewState().maliciousSiteBlocked) {
43404364
command.postValue(ShowAutoconsentAnimation(isCosmetic))

app/src/main/java/com/duckduckgo/app/browser/commands/Command.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ sealed class Command {
6666
data object LaunchNewTab : Command()
6767
data object ResetHistory : Command()
6868
class LaunchPrivacyPro(val uri: Uri) : Command()
69+
data object LaunchVpnManagement : Command()
6970
class DialNumber(val telephoneNumber: String) : Command()
7071
class SendSms(val telephoneNumber: String) : Command()
7172
class SendEmail(val emailAddress: String) : Command()

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

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.duckduckgo.app.browser.databinding.PopupWindowBrowserMenuBottomBindin
2727
import com.duckduckgo.app.browser.menu.BrowserPopupMenu.ResourceType.BOTTOM
2828
import com.duckduckgo.app.browser.menu.BrowserPopupMenu.ResourceType.TOP
2929
import com.duckduckgo.app.browser.viewstate.BrowserViewState
30+
import com.duckduckgo.app.browser.viewstate.VpnMenuState
3031
import com.duckduckgo.common.ui.menu.PopupMenu
3132
import com.duckduckgo.common.ui.view.MenuItemView
3233
import com.duckduckgo.mobile.android.R.drawable
@@ -92,6 +93,13 @@ class BrowserPopupMenu(
9293
}
9394
}
9495

96+
internal val vpnMenuItem: View by lazy {
97+
when (popupMenuResourceType) {
98+
TOP -> topBinding.includeVpnMenuItem.vpnMenuItem
99+
BOTTOM -> bottomBinding.includeVpnMenuItem.vpnMenuItem
100+
}
101+
}
102+
95103
internal val duckChatMenuItem: View by lazy {
96104
when (popupMenuResourceType) {
97105
TOP -> topBinding.includeDuckChatMenuItem.duckChatMenuItem
@@ -256,6 +264,8 @@ class BrowserPopupMenu(
256264

257265
defaultBrowserMenuItem.isVisible = viewState.showSelectDefaultBrowserMenuItem
258266

267+
configureVpnMenuItem(viewState, browserShowing, displayedInCustomTabScreen)
268+
259269
bookmarksMenuItem.isVisible = !displayedInCustomTabScreen
260270
downloadsMenuItem.isVisible = !displayedInCustomTabScreen
261271
settingsMenuItem.isVisible = !displayedInCustomTabScreen
@@ -335,6 +345,74 @@ class BrowserPopupMenu(
335345
}
336346
}
337347

348+
private fun configureVpnMenuItem(
349+
viewState: BrowserViewState,
350+
browserShowing: Boolean,
351+
displayedInCustomTabScreen: Boolean,
352+
) {
353+
// Only show VPN menu item in new tab page overflow menu
354+
val shouldShowVpnMenuItem = !browserShowing && !displayedInCustomTabScreen
355+
356+
when (viewState.vpnMenuState) {
357+
VpnMenuState.Hidden -> {
358+
vpnMenuItem.isVisible = false
359+
}
360+
VpnMenuState.NotSubscribed -> {
361+
vpnMenuItem.isVisible = shouldShowVpnMenuItem
362+
if (shouldShowVpnMenuItem) {
363+
configureVpnMenuItemForNotSubscribed()
364+
}
365+
}
366+
is VpnMenuState.Subscribed -> {
367+
vpnMenuItem.isVisible = shouldShowVpnMenuItem
368+
if (shouldShowVpnMenuItem) {
369+
configureVpnMenuItemForSubscribed(viewState.vpnMenuState.isVpnEnabled)
370+
}
371+
}
372+
}
373+
}
374+
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+
389+
tryForFreePill.isVisible = true
390+
statusIndicator.isVisible = false
391+
menuItemView.setIcon(drawable.ic_vpn_unlocked_24)
392+
}
393+
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+
}
407+
408+
tryForFreePill.isVisible = false
409+
statusIndicator.isVisible = true
410+
statusIndicator.setStatus(isVpnEnabled)
411+
412+
val iconRes = if (isVpnEnabled) drawable.ic_vpn_24 else drawable.ic_vpn_unlocked_24
413+
menuItemView.setIcon(iconRes)
414+
}
415+
338416
enum class ResourceType {
339417
TOP,
340418
BOTTOM,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.browser.menu
18+
19+
import com.duckduckgo.app.browser.viewstate.VpnMenuState
20+
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
21+
import com.duckduckgo.di.scopes.AppScope
22+
import com.duckduckgo.networkprotection.api.NetworkProtectionState
23+
import com.duckduckgo.subscriptions.api.Product.NetP
24+
import com.duckduckgo.subscriptions.api.SubscriptionStatus
25+
import com.duckduckgo.subscriptions.api.Subscriptions
26+
import com.squareup.anvil.annotations.ContributesBinding
27+
import javax.inject.Inject
28+
import kotlinx.coroutines.flow.Flow
29+
import kotlinx.coroutines.flow.combine
30+
31+
interface VpnMenuStateProvider {
32+
fun getVpnMenuState(): Flow<VpnMenuState>
33+
}
34+
35+
@ContributesBinding(AppScope::class)
36+
class VpnMenuStateProviderImpl @Inject constructor(
37+
private val subscriptions: Subscriptions,
38+
private val networkProtectionState: NetworkProtectionState,
39+
private val androidBrowserConfigFeature: AndroidBrowserConfigFeature,
40+
private val vpnMenuStore: VpnMenuStore,
41+
) : VpnMenuStateProvider {
42+
43+
override fun getVpnMenuState(): Flow<VpnMenuState> {
44+
return combine(
45+
subscriptions.getSubscriptionStatusFlow(),
46+
subscriptions.getEntitlementStatus(),
47+
networkProtectionState.getConnectionStateFlow(),
48+
) { subscriptionStatus, entitlements, connectionState ->
49+
if (!androidBrowserConfigFeature.vpnMenuItem().isEnabled()) {
50+
return@combine VpnMenuState.Hidden
51+
}
52+
53+
when {
54+
subscriptionStatus.isActive() && entitlements.contains(NetP) -> {
55+
VpnMenuState.Subscribed(isVpnEnabled = connectionState.isConnected())
56+
}
57+
// User has subscription but no NetP entitlement
58+
subscriptionStatus.isActive() -> VpnMenuState.Hidden
59+
else -> {
60+
if (vpnMenuStore.canShowVpnMenuForNotSubscribed()) {
61+
VpnMenuState.NotSubscribed
62+
} else {
63+
VpnMenuState.Hidden
64+
}
65+
}
66+
}
67+
}
68+
}
69+
}
70+
71+
private fun SubscriptionStatus.isActive(): Boolean {
72+
return when (this) {
73+
SubscriptionStatus.AUTO_RENEWABLE,
74+
SubscriptionStatus.NOT_AUTO_RENEWABLE,
75+
SubscriptionStatus.GRACE_PERIOD,
76+
-> true
77+
else -> false
78+
}
79+
}

0 commit comments

Comments
 (0)