8000 Privacy Pro elegibility (#4311) · CodersSampling/DuckDuckGoAndroid@a2e3a26 · GitHub
[go: up one dir, main page]

Skip to content

Commit a2e3a26

Browse files
Privacy Pro elegibility (duckduckgo#4311)
Task/Issue URL: https://app.asana.com/0/488551667048375/1206765310438669/f ### Description See task ### Steps to test this PR See task --------- Co-authored-by: Lukasz Macionczyk <lukasz.macionczyk@gmail.com>
1 parent 535c395 commit a2e3a26

File tree

14 files changed

+251
-232
lines changed

14 files changed

+251
-232
lines changed

app/src/main/java/com/duckduckgo/app/settings/SettingsViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class SettingsViewModel @Inject constructor(
219219
viewState.value = currentState.copy(
220220
appTrackingProtectionOnboardingShown = appTrackingProtection.isOnboarded(),
221221
appTrackingProtectionEnabled = isDeviceShieldEnabled,
222-
isPrivacyProEnabled = isPrivacyProEnabled,
222+
isPrivacyProEnabled = isPrivacyProEnabled && subscriptions.isEligible(),
223223
networkProtectionEntryState = if (isPrivacyProEnabled) Hidden else currentState.networkProtectionEntryState,
224224
)
225225
delay(1_000)

subscriptions/subscriptions-api/src/main/java/com/duckduckgo/subscriptions/api/Subscriptions.kt

Lin E864 es changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ interface Subscriptions {
3737
* @return `true` if the Privacy Pro product is enabled and live, `false` otherwise
3838
*/
3939
suspend fun isEnabled(): Boolean
40+
41+
/**
42+
* @return `true` if the Privacy Pro product is available for the user, `false` otherwise
43+
*/
44+
suspend fun isEligible(): Boolean
4045
}
4146

4247
enum class Product(val value: String) {

subscriptions/subscriptions-dummy-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsDummy.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ class SubscriptionsDummy @Inject constructor() : Subscriptions {
3232
}
3333

3434
override suspend fun isEnabled(): Boolean = false
35+
36+
override suspend fun isEligible(): Boolean = false
3537
}

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/RealSubscriptions.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.duckduckgo.feature.toggles.api.Toggle.State
2828
import com.duckduckgo.mobile.android.vpn.prefs.VpnSharedPreferencesProvider
2929
import com.duckduckgo.subscriptions.api.Product
3030
import com.duckduckgo.subscriptions.api.Subscriptions
31+
import com.duckduckgo.subscriptions.impl.repository.isActiveOrWaiting
3132
import com.squareup.anvil.annotations.ContributesBinding
3233
import com.squareup.moshi.JsonAdapter
3334
import com.squareup.moshi.Moshi
@@ -62,6 +63,12 @@ class RealSubscriptions @Inject constructor(
6263
override suspend fun isEnabled(): Boolean {
6364
return privacyProFeature.isLaunched().isEnabled()
6465
}
66+
67+
override suspend fun isEligible(): Boolean {
68+
val isActive = subscriptionsManager.subscriptionStatus().isActiveOrWaiting()
69+
val isEligible = subscriptionsManager.getSubscriptionOffer() != null
70+
return isActive || isEligible
71+
}
6572
}
6673

6774
@ContributesRemoteFeature(

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsManager.kt

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package com.duckduckgo.subscriptions.impl
1818

1919
import android.app.Activity
2020
import android.content.Context
21-
import com.android.billingclient.api.ProductDetails
2221
import com.duckduckgo.app.di.AppCoroutineScope
2322
import com.duckduckgo.autofill.api.email.EmailManager
2423
import com.duckduckgo.common.utils.DispatcherProvider
@@ -32,6 +31,9 @@ import com.duckduckgo.subscriptions.impl.SubscriptionStatus.INACTIVE
3231
import com.duckduckgo.subscriptions.impl.SubscriptionStatus.NOT_AUTO_RENEWABLE
3332
import com.duckduckgo.subscriptions.impl.SubscriptionStatus.UNKNOWN
3433
import com.duckduckgo.subscriptions.impl.SubscriptionStatus.WAITING
34+
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.BASIC_SUBSCRIPTION
35+
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.MONTHLY_PLAN
36+
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.YEARLY_PLAN
3537
import com.duckduckgo.subscriptions.impl.billing.PlayBillingManager
3638
import com.duckduckgo.subscriptions.impl.billing.PurchaseState
3739
import com.duckduckgo.subscriptions.impl.billing.RetryPolicy
@@ -73,13 +75,16 @@ import retrofit2.HttpException
7375
interface SubscriptionsManager {
7476

7577
/**
76-
* Launches the purchase flow for a given product details and token
78+
* Returns available purchase options retrieved from Play Store
79+
*/
80+
suspend fun getSubscriptionOffer(): SubscriptionOffer?
81+
82+
/**
83+
* Launches the purchase flow for a given plan id
7784
*/
7885
suspend fun purchase(
7986
activity: Activity,
80-
productDetails: ProductDetails,
81-
offerToken: String,
82-
isReset: Boolean = false,
87+
planId: String,
8388
)
8489

8590
/**
@@ -410,11 +415,24 @@ class RealSubscriptionsManager @Inject constructor(
410415
data class Failure(val message: String) : RecoverSubscriptionResult()
411416
}
412417

418+
override suspend fun getSubscriptionOffer(): SubscriptionOffer? =
419+
playBillingManager.products
420+
.find { it.productId == BASIC_SUBSCRIPTION }
421+
?.run {
422+
val monthlyOffer = subscriptionOfferDetails?.find { it.basePlanId == MONTHLY_PLAN } ?: return@run null
423+
val yearlyOffer = subscriptionOfferDetails?.find { it.basePlanId == YEARLY_PLAN } ?: return@run null
424+
425+
SubscriptionOffer(
426+
monthlyPlanId = monthlyOffer.basePlanId,
427+
monthlyFormattedPrice = monthlyOffer.pricingPhases.pricingPhaseList.first().formattedPrice,
428+
yearlyPlanId = yearlyOffer.basePlanId,
429+
yearlyFormattedPrice = yearlyOffer.pricingPhases.pricingPhaseList.first().formattedPrice,
430+
)
431+
}
432+
413433
override suspend fun purchase(
414434
activity: Activity,
415-
productDetails: ProductDetails,
416-
offerToken: String,
417-
isReset: Boolean,
435+
planId: String,
418436
) {
419437
try {
420438
_currentPurchaseState.emit(CurrentPurchase.PreFlowInProgress)
@@ -447,8 +465,7 @@ class RealSubscriptionsManager @Inject constructor(
447465
withContext(dispatcherProvider.main()) {
448466
playBillingManager.launchBillingFlow(
449467
activity = activity,
450-
productDetails = productDetails,
451-
offerToken = offerToken,
468+
planId = planId,
452469
externalId = authRepository.getAccount()!!.externalId,
453470
)
454471
}
@@ -573,3 +590,10 @@ sealed class CurrentPurchase {
573590
data object Canceled : CurrentPurchase()
574591
data class Failure(val message: String) : CurrentPurchase()
575592
}
593+
594+
data class SubscriptionOffer(
595+
val monthlyPlanId: String,
596+
val monthlyFormattedPrice: String,
597+
val yearlyPlanId: String,
598+
val yearlyFormattedPrice: String,
599+
)

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/billing/BillingUtils.kt

Lines changed: 0 additions & 23 deletions
This file was deleted.

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/billing/PlayBillingManager.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.android.billingclient.api.PurchaseHistoryRecord
2424
import com.duckduckgo.app.di.AppCoroutineScope
2525
import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver
2626
import com.duckduckgo.di.scopes.AppScope
27+
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.BASIC_SUBSCRIPTION
2728
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.LIST_OF_PRODUCTS
2829
import com.duckduckgo.subscriptions.impl.billing.BillingError.ERROR
2930
import com.duckduckgo.subscriptions.impl.billing.BillingError.NETWORK_ERROR
@@ -62,8 +63,7 @@ interface PlayBillingManager {
6263

6364
suspend fun launchBillingFlow(
6465
activity: Activity,
65-
productDetails: ProductDetails,
66-
offerToken: String,
66+
planId: String,
6767
externalId: String,
6868
)
6969
}
@@ -154,15 +154,26 @@ class RealPlayBillingManager @Inject constructor(
154154

155155
override suspend fun launchBillingFlow(
156156
activity: Activity,
157-
productDetails: ProductDetails,
158-
offerToken: String,
157+
planId: String,
159158
externalId: String,
160159
) {
161160
if (!billingClient.ready) {
162161
logcat { "Service not ready" }
163162
connect()
164163
}
165164

165+
val productDetails = products.find { it.productId == BASIC_SUBSCRIPTION }
166+
167+
val offerToken = productDetails
168+
?.subscriptionOfferDetails
169+
?.find { it.basePlanId == planId }
170+
?.offerToken
171+
172+
if (productDetails == null || offerToken == null) {
173+
_purchaseState.emit(Canceled)
174+
return
175+
}
176+
166177
val launchBillingFlowResult = billingClient.launchBillingFlow(
167178
activity = activity,
168179
productDetails = productDetails,

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/repository/SubscriptionsRepository.kt

Lines changed: 0 additions & 46 deletions
This file was deleted.

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModel.kt

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,12 @@ import com.duckduckgo.subscriptions.impl.JSONObjectAdapter
3131
import com.duckduckgo.subscriptions.impl.SubscriptionsChecker
3232
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.ITR
3333
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.MONTHLY
34-
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.MONTHLY_PLAN
3534
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.NETP
3635
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.PIR
3736
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.PLATFORM
3837
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.YEARLY
39-
import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.YEARLY_PLAN
4038
import com.duckduckgo.subscriptions.impl.SubscriptionsManager
41-
import com.duckduckgo.subscriptions.impl.billing.getPrice
4239
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender
43-
import com.duckduckgo.subscriptions.impl.repository.SubscriptionsRepository
4440
import com.duckduckgo.subscriptions.impl.repository.isActive
4541
import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.Command.*
4642
import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.PurchaseStateView.Failure
@@ -69,7 +65,6 @@ class SubscriptionWebViewViewModel @Inject constructor(
6965
private val dispatcherProvider: DispatcherProvider,
7066
private val subscriptionsManager: SubscriptionsManager,
7167
private val subscriptionsChecker: SubscriptionsChecker,
72-
private val subscriptionsRepository: SubscriptionsRepository,
7368
private val networkProtectionWaitlist: NetworkProtectionWaitlist,
7469
private val pixelSender: SubscriptionPixelSender,
7570
) : ViewModel() {
@@ -189,41 +184,43 @@ class SubscriptionWebViewViewModel @Inject constructor(
189184
}
190185
}
191186

192-
fun purchaseSubscription(activity: Activity, id: String) {
187+
fun purchaseSubscription(activity: Activity, planId: String) {
193188
viewModelScope.launch(dispatcherProvider.io()) {
194-
val offerToken = runCatching { subscriptionsRepository.offerDetail()[id]?.offerToken }.getOrNull() ?: return@launch
195-
val productDetails = subscriptionsRepository.subscriptionDetails() ?: return@launch
196-
subscriptionsManager.purchase(activity, productDetails, offerToken, false)
189+
subscriptionsManager.purchase(activity, planId)
197190
}
198191
}
199192

200193
private fun getSubscriptionOptions(featureName: String, method: String, id: String) {
201194
viewModelScope.launch(dispatcherProvider.io()) {
202-
val yearly = subscriptionsRepository.offerDetail()[YEARLY_PLAN]
203-
val monthly = subscriptionsRepository.offerDetail()[MONTHLY_PLAN]
195+
val offer = subscriptionsManager.getSubscriptionOffer()
204196

205-
val yearlyJson = OptionsJson(
206-
id = yearly?.basePlanId!!,
207-
cost = CostJson(displayPrice = yearly.getPrice(), recurrence = YEARLY),
208-
)
197+
val subscriptionOptions = if (offer != null) {
198+
val yearlyJson = OptionsJson(
199+
id = offer.yearlyPlanId,
200+
cost = CostJson(displayPrice = offer.yearlyFormattedPrice, recurrence = YEARLY),
201+
)
209202

210-
val monthlyJson = OptionsJson(
211-
id = monthly?.basePlanId!!,
212-
cost = CostJson(displayPrice = monthly.getPrice(), recurrence = MONTHLY),
213-
)
203+
val monthlyJson = OptionsJson(
204+
id = offer.monthlyPlanId,
205+
cost = CostJson(displayPrice = offer.monthlyFormattedPrice, recurrence = MONTHLY),
206+
)
214207

215-
val subscriptionOptions = jsonAdapter.toJson(
216208
SubscriptionOptionsJson(
217209
options = listOf(yearlyJson, monthlyJson),
218210
features = listOf(FeatureJson(NETP), FeatureJson(ITR), FeatureJson(PIR)),
219-
),
220-
)
211+
)
212+
} else {
213+
SubscriptionOptionsJson(
214+
options = emptyList(),
215+
features = emptyList(),
216+
)
217+
}
221218

222219
val response = JsCallbackData(
223220
featureName = featureName,
224221
method = method,
225222
id = id,
226-
params = JSONObject(subscriptionOptions),
223+
params = JSONObject(jsonAdapter.toJson(subscriptionOptions)),
227224
)
228225
command.send(SendResponseToJs(response))
229226
}

0 commit comments

Comments
 (0)
0