mirror of
https://github.com/ProtonMail/protoncore_android.git
synced 2026-05-15 09:50:41 +00:00
feat(plan): Added Dynamic Plan List Fragment.
This commit is contained in:
@@ -131,11 +131,11 @@ public final class me/proton/core/payment/data/repository/GooglePurchaseReposito
|
||||
}
|
||||
|
||||
public final class me/proton/core/payment/data/repository/PaymentsRepositoryImpl : me/proton/core/payment/domain/repository/PaymentsRepository {
|
||||
public fun <init> (Lme/proton/core/network/data/ApiProvider;)V
|
||||
public fun <init> (Lme/proton/core/network/data/ApiProvider;Lme/proton/core/plan/domain/PlanIconsEndpointProvider;)V
|
||||
public fun createOrUpdateSubscription (Lme/proton/core/domain/entity/UserId;JLme/proton/core/payment/domain/entity/Currency;Lme/proton/core/payment/domain/entity/PaymentTokenEntity;Ljava/util/List;Ljava/util/Map;Lme/proton/core/payment/domain/entity/SubscriptionCycle;Lme/proton/core/payment/domain/entity/SubscriptionManagement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun createPaymentToken (Lme/proton/core/domain/entity/UserId;JLme/proton/core/payment/domain/entity/Currency;Lme/proton/core/payment/domain/entity/PaymentType;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getAvailablePaymentMethods (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getDynamicSubscription (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getDynamicSubscriptions (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getPaymentStatus (Lme/proton/core/domain/entity/UserId;Lme/proton/core/domain/entity/AppStore;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getPaymentTokenStatus-moyEFhY (Lme/proton/core/domain/entity/UserId;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getSubscription (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
|
||||
@@ -24,7 +24,7 @@ import me.proton.core.payment.data.api.request.CreatePaymentToken
|
||||
import me.proton.core.payment.data.api.request.CreateSubscription
|
||||
import me.proton.core.payment.data.api.response.CheckSubscriptionResponse
|
||||
import me.proton.core.payment.data.api.response.CreatePaymentTokenResponse
|
||||
import me.proton.core.payment.data.api.response.DynamicSubscriptionResponse
|
||||
import me.proton.core.payment.data.api.response.DynamicSubscriptionsResponse
|
||||
import me.proton.core.payment.data.api.response.PaymentMethodsResponse
|
||||
import me.proton.core.payment.data.api.response.PaymentStatusResponse
|
||||
import me.proton.core.payment.data.api.response.PaymentTokenStatusResponse
|
||||
@@ -59,7 +59,7 @@ internal interface PaymentsApi : BaseRetrofitApi {
|
||||
suspend fun getCurrentSubscription(): SubscriptionResponse
|
||||
|
||||
@GET("payments/v5/subscription")
|
||||
suspend fun getCurrentDynamicSubscription(): DynamicSubscriptionResponse
|
||||
suspend fun getDynamicSubscriptions(): DynamicSubscriptionsResponse
|
||||
|
||||
/**
|
||||
* Returns the status of payment processors.
|
||||
|
||||
+73
-5
@@ -20,11 +20,79 @@ package me.proton.core.payment.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import me.proton.core.payment.domain.entity.DynamicSubscription
|
||||
import me.proton.core.plan.data.api.response.DynamicEntitlementResource
|
||||
import me.proton.core.plan.data.api.response.DynamicDecorationResource
|
||||
import me.proton.core.plan.data.api.response.toDynamicPlanDecoration
|
||||
import me.proton.core.plan.data.api.response.toDynamicPlanEntitlement
|
||||
import java.time.Instant
|
||||
|
||||
@Serializable
|
||||
internal data class DynamicSubscriptionResponse(
|
||||
@SerialName("Subscription")
|
||||
val subscription: DynamicSubscriptionItemResponse,
|
||||
@SerialName("UpcomingSubscription")
|
||||
val upcomingSubscription: DynamicSubscriptionItemResponse? = null
|
||||
)
|
||||
@SerialName("Name")
|
||||
val name: String,
|
||||
@SerialName("Title")
|
||||
val title: String,
|
||||
@SerialName("Description")
|
||||
val description: String,
|
||||
@SerialName("ParentMetaPlanID")
|
||||
val parentMetaPlanID: String? = null,
|
||||
@SerialName("Type")
|
||||
val type: Int? = null,
|
||||
@SerialName("Cycle")
|
||||
val cycle: Int? = null,
|
||||
@SerialName("CycleDescription")
|
||||
val cycleDescription: String? = null,
|
||||
@SerialName("Currency")
|
||||
val currency: String? = null,
|
||||
@SerialName("Amount")
|
||||
val amount: Long? = null,
|
||||
@SerialName("Offer")
|
||||
val offer: JsonObject? = null,
|
||||
@SerialName("PeriodStart")
|
||||
val periodStart: Long? = null,
|
||||
@SerialName("PeriodEnd")
|
||||
val periodEnd: Long? = null,
|
||||
@SerialName("CreateTime")
|
||||
val createTime: Long? = null,
|
||||
@SerialName("CouponCode")
|
||||
val couponCode: String? = null,
|
||||
@SerialName("Discount")
|
||||
val discount: Long? = null,
|
||||
@SerialName("RenewDiscount")
|
||||
val renewDiscount: Long? = null,
|
||||
@SerialName("RenewAmount")
|
||||
val renewAmount: Long? = null,
|
||||
@SerialName("Renew")
|
||||
val renew: Boolean? = null,
|
||||
@SerialName("External")
|
||||
val external: Boolean? = null,
|
||||
@SerialName("Decorations")
|
||||
val decorations: List<DynamicDecorationResource>? = null,
|
||||
@SerialName("Entitlements")
|
||||
val entitlements: List<DynamicEntitlementResource>? = null
|
||||
) {
|
||||
fun toDynamicSubscription(iconsEndpoint: String): DynamicSubscription = DynamicSubscription(
|
||||
name = name,
|
||||
description = description,
|
||||
parentPlanId = parentMetaPlanID,
|
||||
type = type,
|
||||
title = title,
|
||||
cycleMonths = cycle,
|
||||
cycleDescription = cycleDescription,
|
||||
currency = currency,
|
||||
amount = amount,
|
||||
periodStart = periodStart?.let { Instant.ofEpochSecond(it) },
|
||||
periodEnd = periodEnd?.let { Instant.ofEpochSecond(it) },
|
||||
createTime = createTime?.let { Instant.ofEpochSecond(it) },
|
||||
couponCode = couponCode,
|
||||
discount = discount,
|
||||
renewDiscount = renewDiscount,
|
||||
renewAmount = renewAmount,
|
||||
renew = renew,
|
||||
external = external,
|
||||
decorations = decorations?.mapNotNull { it.toDynamicPlanDecoration() } ?: emptyList(),
|
||||
entitlements = entitlements?.mapNotNull { it.toDynamicPlanEntitlement(iconsEndpoint) } ?: emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.payment.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class DynamicSubscriptionsResponse(
|
||||
@SerialName("Subscriptions")
|
||||
val subscriptions: List<DynamicSubscriptionResponse>,
|
||||
@SerialName("UpcomingSubscriptions")
|
||||
val upcomingSubscriptions: List<DynamicSubscriptionResponse>? = null
|
||||
)
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.payment.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.proton.core.payment.domain.entity.DynamicSubscription
|
||||
import me.proton.core.payment.domain.entity.SubscriptionManagement
|
||||
import me.proton.core.plan.data.api.response.EntitlementResource
|
||||
import me.proton.core.plan.data.api.response.PlanDecorationResource
|
||||
import me.proton.core.plan.data.api.response.toDynamicPlanDecoration
|
||||
import me.proton.core.plan.data.api.response.toDynamicPlanEntitlement
|
||||
import java.time.Instant
|
||||
|
||||
@Serializable
|
||||
internal data class DynamicSubscriptionItemResponse(
|
||||
@SerialName("Name")
|
||||
val name: String? = null,
|
||||
@SerialName("Description")
|
||||
val description: String? = null,
|
||||
@SerialName("ID")
|
||||
val id: String,
|
||||
@SerialName("ParentMetaPlanID")
|
||||
val parentMetaPlanID: String? = null,
|
||||
@SerialName("Type")
|
||||
val type: Int,
|
||||
@SerialName("Title")
|
||||
val title: String,
|
||||
@SerialName("Cycle")
|
||||
val cycle: Int? = null,
|
||||
@SerialName("CycleDescription")
|
||||
val cycleDescription: String? = null,
|
||||
@SerialName("Currency")
|
||||
val currency: String,
|
||||
@SerialName("Amount")
|
||||
val amount: Long,
|
||||
@SerialName("Offer")
|
||||
val offer: String, //??
|
||||
@SerialName("PeriodStart")
|
||||
val periodStart: Long,
|
||||
@SerialName("PeriodEnd")
|
||||
val periodEnd: Long,
|
||||
@SerialName("CreateTime")
|
||||
val createTime: Long,
|
||||
@SerialName("CouponCode")
|
||||
val couponCode: String? = null,
|
||||
@SerialName("Discount")
|
||||
val discount: Long,
|
||||
@SerialName("RenewDiscount")
|
||||
val renewDiscount: Long,
|
||||
@SerialName("RenewAmount")
|
||||
val renewAmount: Long,
|
||||
@SerialName("Renew")
|
||||
val renew: Boolean,
|
||||
@SerialName("External")
|
||||
val external: Boolean,
|
||||
@SerialName("Decorations")
|
||||
val decorations: List<PlanDecorationResource>? = null,
|
||||
@SerialName("Entitlements")
|
||||
val entitlements: List<EntitlementResource>? = null
|
||||
) {
|
||||
fun toDynamicSubscription(): DynamicSubscription = DynamicSubscription(
|
||||
name = name,
|
||||
description = description,
|
||||
id = id,
|
||||
parentPlanId = parentMetaPlanID,
|
||||
type = type,
|
||||
title = title,
|
||||
cycleMonths = cycle,
|
||||
cycleDescription = cycleDescription,
|
||||
currency = currency,
|
||||
amount = amount,
|
||||
offer = offer,
|
||||
periodStart = Instant.ofEpochSecond(periodStart),
|
||||
periodEnd = Instant.ofEpochSecond(periodEnd),
|
||||
createTime = Instant.ofEpochSecond(createTime),
|
||||
couponCode = couponCode,
|
||||
discount = discount,
|
||||
renewDiscount = renewDiscount,
|
||||
renewAmount = renewAmount,
|
||||
renew = renew,
|
||||
external = if (external) SubscriptionManagement.GOOGLE_MANAGED else SubscriptionManagement.PROTON_MANAGED, // TODO: check this
|
||||
decorations = decorations?.mapNotNull { it.toDynamicPlanDecoration() } ?: emptyList(),
|
||||
entitlements = entitlements?.mapNotNull { it.toDynamicPlanEntitlement() } ?: emptyList()
|
||||
)
|
||||
}
|
||||
+13
-11
@@ -44,11 +44,13 @@ import me.proton.core.payment.domain.entity.SubscriptionManagement
|
||||
import me.proton.core.payment.domain.entity.SubscriptionStatus
|
||||
import me.proton.core.payment.domain.repository.PaymentsRepository
|
||||
import me.proton.core.payment.domain.repository.PlanQuantity
|
||||
import me.proton.core.plan.domain.PlanIconsEndpointProvider
|
||||
import me.proton.core.util.kotlin.coroutine.result
|
||||
import javax.inject.Inject
|
||||
|
||||
public class PaymentsRepositoryImpl @Inject constructor(
|
||||
private val provider: ApiProvider
|
||||
private val apiProvider: ApiProvider,
|
||||
private val endpointProvider: PlanIconsEndpointProvider
|
||||
) : PaymentsRepository {
|
||||
|
||||
override suspend fun createPaymentToken(
|
||||
@@ -107,7 +109,7 @@ public class PaymentsRepositoryImpl @Inject constructor(
|
||||
paymentMethodId = paymentType.paymentMethodId
|
||||
)
|
||||
}
|
||||
provider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
apiProvider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
createPaymentToken(request).toCreatePaymentTokenResult()
|
||||
}.valueOrThrow
|
||||
}
|
||||
@@ -116,14 +118,14 @@ public class PaymentsRepositoryImpl @Inject constructor(
|
||||
sessionUserId: SessionUserId?,
|
||||
paymentToken: ProtonPaymentToken
|
||||
): PaymentTokenResult.PaymentTokenStatusResult =
|
||||
provider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
apiProvider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
getPaymentTokenStatus(paymentToken.value).toPaymentTokenStatusResult()
|
||||
}.valueOrThrow
|
||||
|
||||
override suspend fun getAvailablePaymentMethods(
|
||||
sessionUserId: SessionUserId
|
||||
): List<PaymentMethod> = result("getAvailablePaymentMethods") {
|
||||
provider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
apiProvider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
getPaymentMethods().paymentMethods.map {
|
||||
PaymentMethod(it.id, PaymentMethodType.map[it.type] ?: PaymentMethodType.CARD, it.toDetails())
|
||||
}
|
||||
@@ -137,7 +139,7 @@ public class PaymentsRepositoryImpl @Inject constructor(
|
||||
currency: Currency,
|
||||
cycle: SubscriptionCycle
|
||||
): SubscriptionStatus = result("validateSubscription") {
|
||||
provider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
apiProvider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
validateSubscription(
|
||||
CheckSubscription(codes, plans, currency.name, cycle.value)
|
||||
).toSubscriptionStatus()
|
||||
@@ -145,13 +147,13 @@ public class PaymentsRepositoryImpl @Inject constructor(
|
||||
}
|
||||
|
||||
override suspend fun getSubscription(sessionUserId: SessionUserId): Subscription? =
|
||||
provider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
apiProvider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
getCurrentSubscription().subscription.toSubscription()
|
||||
}.valueOrThrow
|
||||
|
||||
override suspend fun getDynamicSubscription(sessionUserId: SessionUserId): DynamicSubscription =
|
||||
provider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
getCurrentDynamicSubscription().subscription.toDynamicSubscription()
|
||||
override suspend fun getDynamicSubscriptions(sessionUserId: SessionUserId): List<DynamicSubscription> =
|
||||
apiProvider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
getDynamicSubscriptions().subscriptions.map { it.toDynamicSubscription(endpointProvider.get()) }
|
||||
}.valueOrThrow
|
||||
|
||||
override suspend fun createOrUpdateSubscription(
|
||||
@@ -164,7 +166,7 @@ public class PaymentsRepositoryImpl @Inject constructor(
|
||||
cycle: SubscriptionCycle,
|
||||
subscriptionManagement: SubscriptionManagement
|
||||
): Subscription = result("createOrUpdateSubscription") {
|
||||
provider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
apiProvider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
createUpdateSubscription(
|
||||
body = CreateSubscription(
|
||||
amount = amount,
|
||||
@@ -184,7 +186,7 @@ public class PaymentsRepositoryImpl @Inject constructor(
|
||||
AppStore.FDroid -> "fdroid"
|
||||
AppStore.GooglePlay -> "google"
|
||||
}
|
||||
return provider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
return apiProvider.get<PaymentsApi>(sessionUserId).invoke {
|
||||
paymentStatus(appStoreCode).toPaymentStatus()
|
||||
}.valueOrThrow
|
||||
}
|
||||
|
||||
@@ -134,11 +134,11 @@ class PaymentsApiTest {
|
||||
webServer.enqueueFromResourceFile("GET/payments/v4/dynamic-subscription.json", javaClass.classLoader)
|
||||
|
||||
// When
|
||||
val subscription = tested.getCurrentDynamicSubscription().subscription.toDynamicSubscription()
|
||||
val subscription = tested.getDynamicSubscriptions().subscriptions.first().toDynamicSubscription("endpoint")
|
||||
|
||||
// Then
|
||||
assertEquals(28788, subscription.amount)
|
||||
assertEquals(SubscriptionManagement.PROTON_MANAGED, subscription.external)
|
||||
assertEquals(false, subscription.external)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+5
-1
@@ -49,6 +49,7 @@ import me.proton.core.payment.domain.entity.Subscription
|
||||
import me.proton.core.payment.domain.entity.SubscriptionCycle
|
||||
import me.proton.core.payment.domain.entity.SubscriptionManagement
|
||||
import me.proton.core.payment.domain.entity.SubscriptionStatus
|
||||
import me.proton.core.plan.domain.PlanIconsEndpointProvider
|
||||
import me.proton.core.test.kotlin.TestDispatcherProvider
|
||||
import me.proton.core.test.kotlin.assertIs
|
||||
import me.proton.core.test.kotlin.runTestWithResultContext
|
||||
@@ -62,6 +63,9 @@ import kotlin.test.assertTrue
|
||||
class PaymentsRepositoryImplTest {
|
||||
|
||||
// region mocks
|
||||
private val endpointProvider = mockk<PlanIconsEndpointProvider> {
|
||||
every { get() } returns "endpoint"
|
||||
}
|
||||
private val sessionProvider = mockk<SessionProvider>(relaxed = true)
|
||||
private val apiManagerFactory = mockk<ApiManagerFactory>(relaxed = true)
|
||||
private val apiManager = mockk<ApiManager<PaymentsApi>>(relaxed = true)
|
||||
@@ -100,7 +104,7 @@ class PaymentsRepositoryImplTest {
|
||||
interfaceClass = PaymentsApi::class
|
||||
)
|
||||
} returns apiManager
|
||||
repository = PaymentsRepositoryImpl(apiProvider)
|
||||
repository = PaymentsRepositoryImpl(apiProvider, endpointProvider)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,55 +1,53 @@
|
||||
{
|
||||
"Code": 1000,
|
||||
"Subscription": {
|
||||
"Name": "Name of primary plan (Type 1)",
|
||||
"Description": "Current plan",
|
||||
"ID": "6opBd5UdUtY_RtEz...YA==",
|
||||
"ParentMetaPlanID": "hUcV0_EeNw...g==",
|
||||
"Type": 1,
|
||||
"Title": "Visionary",
|
||||
"Cycle": 12,
|
||||
"CycleDescription": "1 year",
|
||||
"Currency": "USD",
|
||||
"Amount": 28788,
|
||||
"Offer": "default",
|
||||
|
||||
"PeriodStart": 1665402858,
|
||||
"PeriodEnd": 1696938858,
|
||||
"CreateTime": 1570708458,
|
||||
"CouponCode": "PROTONTEAM",
|
||||
"Discount": -28788,
|
||||
"RenewDiscount": -28788,
|
||||
"RenewAmount": 0,
|
||||
|
||||
"Renew": true,
|
||||
"External": false,
|
||||
|
||||
"Entitlements": [
|
||||
{
|
||||
"Type": "progress",
|
||||
"Text": "19.55 MB of 15 GB",
|
||||
"Min": 0,
|
||||
"Max": 16106127360,
|
||||
"Current": 20503730,
|
||||
"Icon": "tick",
|
||||
"IconName": "tick"
|
||||
},
|
||||
{
|
||||
"Type": "description",
|
||||
"Text": "500 GB storage",
|
||||
"Icon": "tick",
|
||||
"IconName": "tick",
|
||||
"Hint": "You win a lot of storage"
|
||||
}
|
||||
],
|
||||
|
||||
"Decorations": [{
|
||||
"Type": "Star",
|
||||
"Icon": "base64"
|
||||
}, {
|
||||
"Type": "Border",
|
||||
"Color": "#AABBCC"
|
||||
}]
|
||||
},
|
||||
"UpcomingSubscription": null
|
||||
"Subscriptions": [
|
||||
{
|
||||
"Name": "Name of primary plan (Type 1)",
|
||||
"Title": "Visionary",
|
||||
"Description": "Current plan",
|
||||
"ParentMetaPlanID": "hUcV0_EeNw...g==",
|
||||
"Type": 1,
|
||||
"Cycle": 12,
|
||||
"CycleDescription": "1 year",
|
||||
"Currency": "USD",
|
||||
"Amount": 28788,
|
||||
"Offer": null,
|
||||
"PeriodStart": 1665402858,
|
||||
"PeriodEnd": 1696938858,
|
||||
"CreateTime": 1570708458,
|
||||
"CouponCode": "PROTONTEAM",
|
||||
"Discount": -28788,
|
||||
"RenewDiscount": -28788,
|
||||
"RenewAmount": 0,
|
||||
"Renew": true,
|
||||
"External": false,
|
||||
"Entitlements": [
|
||||
{
|
||||
"Type": "storage",
|
||||
"Text": "19.55 MB of 15 GB",
|
||||
"Min": 0,
|
||||
"Max": 16106127360,
|
||||
"Current": 20503730,
|
||||
"IconName": "tick"
|
||||
},
|
||||
{
|
||||
"Type": "description",
|
||||
"Text": "500 GB storage",
|
||||
"IconName": "tick",
|
||||
"Hint": "You win a lot of storage"
|
||||
}
|
||||
],
|
||||
"Decorations": [
|
||||
{
|
||||
"Type": "Star",
|
||||
"IconName": "base64"
|
||||
},
|
||||
{
|
||||
"Type": "Border",
|
||||
"Color": "#AABBCC"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UpcomingSubscriptions": null
|
||||
}
|
||||
|
||||
@@ -118,34 +118,32 @@ public final class me/proton/core/payment/domain/entity/Details$PayPalDetails :
|
||||
}
|
||||
|
||||
public final class me/proton/core/payment/domain/entity/DynamicSubscription {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;ZLme/proton/core/payment/domain/entity/SubscriptionManagement;Ljava/util/List;Ljava/util/List;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;ZLme/proton/core/payment/domain/entity/SubscriptionManagement;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/util/List;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun component10 ()J
|
||||
public final fun component11 ()Ljava/lang/String;
|
||||
public final fun component10 ()Ljava/time/Instant;
|
||||
public final fun component11 ()Ljava/time/Instant;
|
||||
public final fun component12 ()Ljava/time/Instant;
|
||||
public final fun component13 ()Ljava/time/Instant;
|
||||
public final fun component14 ()Ljava/time/Instant;
|
||||
public final fun component15 ()Ljava/lang/String;
|
||||
public final fun component13 ()Ljava/lang/String;
|
||||
public final fun component14 ()Ljava/lang/Long;
|
||||
public final fun component15 ()Ljava/lang/Long;
|
||||
public final fun component16 ()Ljava/lang/Long;
|
||||
public final fun component17 ()Ljava/lang/Long;
|
||||
public final fun component18 ()Ljava/lang/Long;
|
||||
public final fun component19 ()Z
|
||||
public final fun component17 ()Ljava/lang/Boolean;
|
||||
public final fun component18 ()Ljava/lang/Boolean;
|
||||
public final fun component19 ()Ljava/util/List;
|
||||
public final fun component2 ()Ljava/lang/String;
|
||||
public final fun component20 ()Lme/proton/core/payment/domain/entity/SubscriptionManagement;
|
||||
public final fun component21 ()Ljava/util/List;
|
||||
public final fun component22 ()Ljava/util/List;
|
||||
public final fun component20 ()Ljava/util/List;
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun component4 ()Ljava/lang/String;
|
||||
public final fun component5 ()I
|
||||
public final fun component6 ()Ljava/lang/String;
|
||||
public final fun component7 ()Ljava/lang/Integer;
|
||||
public final fun component5 ()Ljava/lang/Integer;
|
||||
public final fun component6 ()Ljava/lang/Integer;
|
||||
public final fun component7 ()Ljava/lang/String;
|
||||
public final fun component8 ()Ljava/lang/String;
|
||||
public final fun component9 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;ZLme/proton/core/payment/domain/entity/SubscriptionManagement;Ljava/util/List;Ljava/util/List;)Lme/proton/core/payment/domain/entity/DynamicSubscription;
|
||||
public static synthetic fun copy$default (Lme/proton/core/payment/domain/entity/DynamicSubscription;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;ZLme/proton/core/payment/domain/entity/SubscriptionManagement;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lme/proton/core/payment/domain/entity/DynamicSubscription;
|
||||
public final fun component9 ()Ljava/lang/Long;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/util/List;)Lme/proton/core/payment/domain/entity/DynamicSubscription;
|
||||
public static synthetic fun copy$default (Lme/proton/core/payment/domain/entity/DynamicSubscription;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/time/Instant;Ljava/time/Instant;Ljava/time/Instant;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lme/proton/core/payment/domain/entity/DynamicSubscription;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getAmount ()J
|
||||
public final fun getAmount ()Ljava/lang/Long;
|
||||
public final fun getCouponCode ()Ljava/lang/String;
|
||||
public final fun getCreateTime ()Ljava/time/Instant;
|
||||
public final fun getCurrency ()Ljava/lang/String;
|
||||
@@ -155,18 +153,16 @@ public final class me/proton/core/payment/domain/entity/DynamicSubscription {
|
||||
public final fun getDescription ()Ljava/lang/String;
|
||||
public final fun getDiscount ()Ljava/lang/Long;
|
||||
public final fun getEntitlements ()Ljava/util/List;
|
||||
public final fun getExternal ()Lme/proton/core/payment/domain/entity/SubscriptionManagement;
|
||||
public final fun getId ()Ljava/lang/String;
|
||||
public final fun getExternal ()Ljava/lang/Boolean;
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
public final fun getOffer ()Ljava/lang/String;
|
||||
public final fun getParentPlanId ()Ljava/lang/String;
|
||||
public final fun getPeriodEnd ()Ljava/time/Instant;
|
||||
public final fun getPeriodStart ()Ljava/time/Instant;
|
||||
public final fun getRenew ()Z
|
||||
public final fun getRenew ()Ljava/lang/Boolean;
|
||||
public final fun getRenewAmount ()Ljava/lang/Long;
|
||||
public final fun getRenewDiscount ()Ljava/lang/Long;
|
||||
public final fun getTitle ()Ljava/lang/String;
|
||||
public final fun getType ()I
|
||||
public final fun getType ()Ljava/lang/Integer;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
@@ -464,7 +460,7 @@ public abstract interface class me/proton/core/payment/domain/repository/Payment
|
||||
public abstract fun createOrUpdateSubscription (Lme/proton/core/domain/entity/UserId;JLme/proton/core/payment/domain/entity/Currency;Lme/proton/core/payment/domain/entity/PaymentTokenEntity;Ljava/util/List;Ljava/util/Map;Lme/proton/core/payment/domain/entity/SubscriptionCycle;Lme/proton/core/payment/domain/entity/SubscriptionManagement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun createPaymentToken (Lme/proton/core/domain/entity/UserId;JLme/proton/core/payment/domain/entity/Currency;Lme/proton/core/payment/domain/entity/PaymentType;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getAvailablePaymentMethods (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getDynamicSubscription (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getDynamicSubscriptions (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getPaymentStatus (Lme/proton/core/domain/entity/UserId;Lme/proton/core/domain/entity/AppStore;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getPaymentTokenStatus-moyEFhY (Lme/proton/core/domain/entity/UserId;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getSubscription (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
|
||||
+16
-17
@@ -18,36 +18,35 @@
|
||||
|
||||
package me.proton.core.payment.domain.entity
|
||||
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanDecoration
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanEntitlement
|
||||
import me.proton.core.plan.domain.entity.DynamicDecoration
|
||||
import me.proton.core.plan.domain.entity.DynamicEntitlement
|
||||
import java.time.Instant
|
||||
|
||||
public data class DynamicSubscription(
|
||||
val name: String? = null,
|
||||
val description: String? = null,
|
||||
val id: String,
|
||||
val parentPlanId: String? = null,
|
||||
val type: Int,
|
||||
val name: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
|
||||
val parentPlanId: String? = null,
|
||||
val type: Int? = null,
|
||||
|
||||
val cycleMonths: Int? = null,
|
||||
val cycleDescription: String? = null,
|
||||
val currency: String,
|
||||
val amount: Long,
|
||||
val offer: String? = null,
|
||||
val currency: String? = null,
|
||||
val amount: Long? = null,
|
||||
|
||||
val periodStart: Instant,
|
||||
val periodEnd: Instant,
|
||||
val createTime: Instant,
|
||||
val periodStart: Instant? = null,
|
||||
val periodEnd: Instant? = null,
|
||||
val createTime: Instant? = null,
|
||||
|
||||
val couponCode: String? = null,
|
||||
val discount: Long? = null,
|
||||
val renewDiscount: Long? = null,
|
||||
val renewAmount: Long? = null,
|
||||
|
||||
val renew: Boolean,
|
||||
val external: SubscriptionManagement? = null,
|
||||
val renew: Boolean? = null,
|
||||
val external: Boolean? = null,
|
||||
|
||||
val decorations: List<DynamicPlanDecoration> = emptyList(),
|
||||
val entitlements: List<DynamicPlanEntitlement> = emptyList()
|
||||
val decorations: List<DynamicDecoration> = emptyList(),
|
||||
val entitlements: List<DynamicEntitlement> = emptyList()
|
||||
)
|
||||
|
||||
+2
-2
@@ -91,9 +91,9 @@ public interface PaymentsRepository {
|
||||
|
||||
/**
|
||||
* Authenticated.
|
||||
* Returns current active dynamic subscription.
|
||||
* Returns current active dynamic subscriptions.
|
||||
*/
|
||||
public suspend fun getDynamicSubscription(sessionUserId: SessionUserId): DynamicSubscription
|
||||
public suspend fun getDynamicSubscriptions(sessionUserId: SessionUserId): List<DynamicSubscription>
|
||||
|
||||
/**
|
||||
* Authenticated.
|
||||
|
||||
+1
-1
@@ -32,6 +32,6 @@ public class GetDynamicSubscription @Inject constructor(
|
||||
private val plansRepository: PaymentsRepository
|
||||
) {
|
||||
public suspend operator fun invoke(userId: UserId): DynamicSubscription {
|
||||
return plansRepository.getDynamicSubscription(userId)
|
||||
return plansRepository.getDynamicSubscriptions(userId).first()
|
||||
}
|
||||
}
|
||||
|
||||
+10
-20
@@ -18,31 +18,22 @@
|
||||
|
||||
package me.proton.core.payment.domain.entity
|
||||
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanDecoration
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanEntitlement
|
||||
import me.proton.core.plan.domain.entity.DynamicDecoration
|
||||
import me.proton.core.plan.domain.entity.DynamicEntitlement
|
||||
import java.time.Instant
|
||||
import java.util.Base64
|
||||
|
||||
private const val PLAN_ICON_SVG = """
|
||||
<svg width="204" height="204" xmlns="http://www.w3.org/2000/svg">
|
||||
<g><ellipse stroke-width="4" stroke="#000" ry="100" rx="100" id="svg_1" cy="102" cx="102" fill="#fff"/></g>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
private val planIconBase64 =
|
||||
Base64.getEncoder().encode(PLAN_ICON_SVG.toByteArray()).decodeToString()
|
||||
|
||||
val dynamicSubscription = DynamicSubscription(
|
||||
id = "DgauUA2dU6_ufQculMB1b_wecb3D2PraQlfPbknlonENxSm88iiMOkMBPfa0gKEhtdbv_gu4t_CRN6PEu0DQuw==",
|
||||
amount = 0,
|
||||
title = "Title",
|
||||
name = "free",
|
||||
title = "Proton Free",
|
||||
description = "Current Plan",
|
||||
type = 1,
|
||||
createTime = Instant.ofEpochSecond(1_570_708_458),
|
||||
currency = "CHF",
|
||||
cycleDescription = "1 year",
|
||||
cycleMonths = 12,
|
||||
discount = -28_788,
|
||||
external = SubscriptionManagement.PROTON_MANAGED,
|
||||
amount = 0,
|
||||
external = false,
|
||||
periodStart = Instant.ofEpochSecond(1_665_402_858),
|
||||
periodEnd = Instant.ofEpochSecond(1_696_938_858),
|
||||
renew = true,
|
||||
@@ -50,12 +41,11 @@ val dynamicSubscription = DynamicSubscription(
|
||||
renewAmount = 0,
|
||||
couponCode = "COUPON123",
|
||||
decorations = listOf(
|
||||
DynamicPlanDecoration.Star(iconBase64 = planIconBase64)
|
||||
DynamicDecoration.Star(iconName = "tick")
|
||||
),
|
||||
entitlements = listOf(
|
||||
DynamicPlanEntitlement.Description(
|
||||
iconBase64 = planIconBase64,
|
||||
iconName = "tick",
|
||||
DynamicEntitlement.Description(
|
||||
iconUrl = "tick",
|
||||
text = "Up to 1 GB storage",
|
||||
hint = "Start with 500 MB and unlock more storage along the way."
|
||||
)
|
||||
|
||||
+5
-5
@@ -41,6 +41,7 @@ class GetDynamicSubscriptionTest {
|
||||
// region test data
|
||||
private val testUserId = UserId("test-user-id")
|
||||
private val testSubscription = dynamicSubscription
|
||||
private val testSubscriptions = listOf(testSubscription)
|
||||
// endregion
|
||||
|
||||
private lateinit var useCase: GetDynamicSubscription
|
||||
@@ -53,20 +54,19 @@ class GetDynamicSubscriptionTest {
|
||||
@Test
|
||||
fun `get subscription returns success, enqueue getsubscription observability success`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { repository.getDynamicSubscription(testUserId) } returns testSubscription
|
||||
coEvery { repository.getDynamicSubscriptions(testUserId) } returns testSubscriptions
|
||||
// WHEN
|
||||
val result = useCase.invoke(testUserId)
|
||||
// THEN
|
||||
assertEquals(testSubscription, result)
|
||||
assertNotNull(result)
|
||||
assertNotNull(result)
|
||||
assertEquals(0, result.amount)
|
||||
assertEquals(0L, result.amount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get subscription returns error`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { repository.getDynamicSubscription(testUserId) } throws ApiException(
|
||||
coEvery { repository.getDynamicSubscriptions(testUserId) } throws ApiException(
|
||||
ApiResult.Error.Connection(
|
||||
false,
|
||||
RuntimeException("Test error")
|
||||
@@ -84,7 +84,7 @@ class GetDynamicSubscriptionTest {
|
||||
@Test
|
||||
fun `get dynamic subscription returns no active subscription`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { repository.getDynamicSubscription(testUserId) } throws ApiException(
|
||||
coEvery { repository.getDynamicSubscriptions(testUserId) } throws ApiException(
|
||||
ApiResult.Error.Http(
|
||||
httpCode = 123,
|
||||
"http error",
|
||||
|
||||
@@ -10,6 +10,7 @@ public final class me/proton/core/plan/dagger/BuildConfig {
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/plan/dagger/CorePlanModule {
|
||||
public abstract fun provideIconsEndpoint (Lme/proton/core/plan/data/PlanIconsEndpointProviderImpl;)Lme/proton/core/plan/domain/PlanIconsEndpointProvider;
|
||||
public abstract fun providePlansRepository (Lme/proton/core/plan/data/repository/PlansRepositoryImpl;)Lme/proton/core/plan/domain/repository/PlansRepository;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import me.proton.core.plan.data.PlanIconsEndpointProviderImpl
|
||||
import me.proton.core.plan.data.repository.PlansRepositoryImpl
|
||||
import me.proton.core.plan.domain.PlanIconsEndpointProvider
|
||||
import me.proton.core.plan.domain.repository.PlansRepository
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -33,4 +35,8 @@ public interface CorePlanModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
public fun providePlansRepository(impl: PlansRepositoryImpl): PlansRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
public fun provideIconsEndpoint(impl: PlanIconsEndpointProviderImpl): PlanIconsEndpointProvider
|
||||
}
|
||||
|
||||
+119
-116
@@ -17,194 +17,197 @@ public final class me/proton/core/plan/data/IsDynamicPlanEnabledImpl$Companion {
|
||||
public final fun getFeatureId ()Lme/proton/core/featureflag/domain/entity/FeatureId;
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/data/api/response/EntitlementResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/EntitlementResource$Companion;
|
||||
public final class me/proton/core/plan/data/PlanIconsEndpointProviderImpl : me/proton/core/plan/domain/PlanIconsEndpointProvider {
|
||||
public fun <init> (Lokhttp3/HttpUrl;Lme/proton/core/network/domain/NetworkPrefs;)V
|
||||
public fun get ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Companion {
|
||||
public abstract class me/proton/core/plan/data/api/response/DynamicDecorationResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Companion;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResource$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Description : me/proton/core/plan/data/api/response/EntitlementResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/EntitlementResource$Description$Companion;
|
||||
public synthetic fun <init> (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResource$Star : me/proton/core/plan/data/api/response/DynamicDecorationResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Star$Companion;
|
||||
public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;)Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Star;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Star;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Star;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getIconName ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Star;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResource$Star$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Star$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Star;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Star;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResource$Star$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown : me/proton/core/plan/data/api/response/DynamicDecorationResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown$Companion;
|
||||
public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;)Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getType ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResource$Unknown$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResourceKt {
|
||||
public static final fun toDynamicPlanDecoration (Lme/proton/core/plan/data/api/response/DynamicDecorationResource;)Lme/proton/core/plan/domain/entity/DynamicDecoration;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicDecorationResourceSerializer : kotlinx/serialization/json/JsonContentPolymorphicSerializer {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/data/api/response/DynamicEntitlementResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Companion;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Description : me/proton/core/plan/data/api/response/DynamicEntitlementResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Description$Companion;
|
||||
public synthetic fun <init> (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun component2 ()Ljava/lang/String;
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun component4 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lme/proton/core/plan/data/api/response/EntitlementResource$Description;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/EntitlementResource$Description;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/EntitlementResource$Description;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Description;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Description;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Description;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getHint ()Ljava/lang/String;
|
||||
public final fun getIcon ()Ljava/lang/String;
|
||||
public final fun getIconName ()Ljava/lang/String;
|
||||
public final fun getText ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/EntitlementResource$Description;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Description;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Description$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/EntitlementResource$Description$$serializer;
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Description$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Description$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/EntitlementResource$Description;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Description;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/EntitlementResource$Description;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Description;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Description$Companion {
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Description$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Storage : me/proton/core/plan/data/api/response/EntitlementResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/EntitlementResource$Storage$Companion;
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage : me/proton/core/plan/data/api/response/DynamicEntitlementResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage$Companion;
|
||||
public synthetic fun <init> (IJJLkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (JJ)V
|
||||
public final fun component1 ()J
|
||||
public final fun component2 ()J
|
||||
public final fun copy (JJ)Lme/proton/core/plan/data/api/response/EntitlementResource$Storage;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/EntitlementResource$Storage;JJILjava/lang/Object;)Lme/proton/core/plan/data/api/response/EntitlementResource$Storage;
|
||||
public final fun copy (JJ)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage;JJILjava/lang/Object;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getCurrent ()J
|
||||
public final fun getMax ()J
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/EntitlementResource$Storage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Storage$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/EntitlementResource$Storage$$serializer;
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/EntitlementResource$Storage;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/EntitlementResource$Storage;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Storage$Companion {
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Storage$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Unknown : me/proton/core/plan/data/api/response/EntitlementResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/EntitlementResource$Unknown$Companion;
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown : me/proton/core/plan/data/api/response/DynamicEntitlementResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown$Companion;
|
||||
public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;)Lme/proton/core/plan/data/api/response/EntitlementResource$Unknown;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/EntitlementResource$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/EntitlementResource$Unknown;
|
||||
public final fun copy (Ljava/lang/String;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getType ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/EntitlementResource$Unknown;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Unknown$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/EntitlementResource$Unknown$$serializer;
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/EntitlementResource$Unknown;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/EntitlementResource$Unknown;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResource$Unknown$Companion {
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Unknown$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResourceKt {
|
||||
public static final fun toDynamicPlanEntitlement (Lme/proton/core/plan/data/api/response/EntitlementResource;)Lme/proton/core/plan/domain/entity/DynamicPlanEntitlement;
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResourceKt {
|
||||
public static final fun toDynamicPlanEntitlement (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource;Ljava/lang/String;)Lme/proton/core/plan/domain/entity/DynamicEntitlement;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/EntitlementResourceSerializer : kotlinx/serialization/json/JsonContentPolymorphicSerializer {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/data/api/response/PlanDecorationResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/PlanDecorationResource$Companion;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResource$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResource$Star : me/proton/core/plan/data/api/response/PlanDecorationResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/PlanDecorationResource$Star$Companion;
|
||||
public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;)Lme/proton/core/plan/data/api/response/PlanDecorationResource$Star;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/PlanDecorationResource$Star;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/PlanDecorationResource$Star;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getIcon ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/PlanDecorationResource$Star;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResource$Star$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/PlanDecorationResource$Star$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/PlanDecorationResource$Star;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/PlanDecorationResource$Star;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResource$Star$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResource$Unknown : me/proton/core/plan/data/api/response/PlanDecorationResource {
|
||||
public static final field Companion Lme/proton/core/plan/data/api/response/PlanDecorationResource$Unknown$Companion;
|
||||
public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;)Lme/proton/core/plan/data/api/response/PlanDecorationResource$Unknown;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/PlanDecorationResource$Unknown;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/PlanDecorationResource$Unknown;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getType ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
public static final fun write$Self (Lme/proton/core/plan/data/api/response/PlanDecorationResource$Unknown;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResource$Unknown$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
|
||||
public static final field INSTANCE Lme/proton/core/plan/data/api/response/PlanDecorationResource$Unknown$$serializer;
|
||||
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
|
||||
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/plan/data/api/response/PlanDecorationResource$Unknown;
|
||||
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
|
||||
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
|
||||
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/plan/data/api/response/PlanDecorationResource$Unknown;)V
|
||||
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResource$Unknown$Companion {
|
||||
public final fun serializer ()Lkotlinx/serialization/KSerializer;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResourceKt {
|
||||
public static final fun toDynamicPlanDecoration (Lme/proton/core/plan/data/api/response/PlanDecorationResource;)Lme/proton/core/plan/domain/entity/DynamicPlanDecoration;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/api/response/PlanDecorationResourceSerializer : kotlinx/serialization/json/JsonContentPolymorphicSerializer {
|
||||
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResourceSerializer : kotlinx/serialization/json/JsonContentPolymorphicSerializer {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/data/repository/PlansRepositoryImpl : me/proton/core/plan/domain/repository/PlansRepository {
|
||||
public fun <init> (Lme/proton/core/network/data/ApiProvider;)V
|
||||
public fun <init> (Lme/proton/core/network/data/ApiProvider;Lme/proton/core/plan/domain/PlanIconsEndpointProvider;)V
|
||||
public fun getDynamicPlans (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getPlans (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun getPlansDefault (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
|
||||
@@ -29,7 +29,7 @@ protonBuild {
|
||||
|
||||
protonCoverage {
|
||||
minBranchCoveragePercentage.set(59)
|
||||
minLineCoveragePercentage.set(88)
|
||||
minLineCoveragePercentage.set(84)
|
||||
}
|
||||
|
||||
publishOption.shouldBePublishedAsLib = true
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.data
|
||||
|
||||
import me.proton.core.network.data.di.BaseProtonApiUrl
|
||||
import me.proton.core.network.domain.NetworkPrefs
|
||||
import me.proton.core.plan.domain.PlanIconsEndpointProvider
|
||||
import okhttp3.HttpUrl
|
||||
import javax.inject.Inject
|
||||
|
||||
class PlanIconsEndpointProviderImpl @Inject constructor(
|
||||
@BaseProtonApiUrl
|
||||
private val baseProtonApiUrl: HttpUrl,
|
||||
private val networkPrefs: NetworkPrefs,
|
||||
) : PlanIconsEndpointProvider {
|
||||
|
||||
override fun get(): String = when (networkPrefs.activeAltBaseUrl) {
|
||||
null -> "$baseProtonApiUrl/payments/v5/resources/icons/"
|
||||
else -> "${networkPrefs.activeAltBaseUrl}/payments/v5/resources/icons/"
|
||||
}
|
||||
}
|
||||
+15
-22
@@ -18,7 +18,6 @@
|
||||
|
||||
package me.proton.core.plan.data.api.response
|
||||
|
||||
import android.util.Base64
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -27,41 +26,35 @@ import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanDecoration
|
||||
import me.proton.core.plan.domain.entity.DynamicDecoration
|
||||
|
||||
@Serializable(PlanDecorationResourceSerializer::class)
|
||||
sealed class PlanDecorationResource {
|
||||
@Serializable(DynamicDecorationResourceSerializer::class)
|
||||
sealed class DynamicDecorationResource {
|
||||
@Serializable
|
||||
data class Star(
|
||||
@SerialName("Icon")
|
||||
val icon: String
|
||||
) : PlanDecorationResource()
|
||||
@SerialName("IconName")
|
||||
val iconName: String
|
||||
) : DynamicDecorationResource()
|
||||
|
||||
@Serializable
|
||||
data class Unknown(
|
||||
@SerialName("Type")
|
||||
val type: String
|
||||
) : PlanDecorationResource()
|
||||
) : DynamicDecorationResource()
|
||||
}
|
||||
|
||||
fun PlanDecorationResource.toDynamicPlanDecoration(): DynamicPlanDecoration? =
|
||||
fun DynamicDecorationResource.toDynamicPlanDecoration(): DynamicDecoration? =
|
||||
when (this) {
|
||||
is PlanDecorationResource.Star -> DynamicPlanDecoration.Star(
|
||||
Base64.decode(
|
||||
icon,
|
||||
Base64.DEFAULT
|
||||
).decodeToString()
|
||||
)
|
||||
|
||||
is PlanDecorationResource.Unknown -> null
|
||||
is DynamicDecorationResource.Star -> DynamicDecoration.Star(iconName)
|
||||
is DynamicDecorationResource.Unknown -> null
|
||||
}
|
||||
|
||||
class PlanDecorationResourceSerializer :
|
||||
JsonContentPolymorphicSerializer<PlanDecorationResource>(PlanDecorationResource::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out PlanDecorationResource> {
|
||||
class DynamicDecorationResourceSerializer :
|
||||
JsonContentPolymorphicSerializer<DynamicDecorationResource>(DynamicDecorationResource::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out DynamicDecorationResource> {
|
||||
return when (element.jsonObject["Type"]?.jsonPrimitive?.contentOrNull) {
|
||||
"Star" -> PlanDecorationResource.Star.serializer()
|
||||
else -> PlanDecorationResource.Unknown.serializer()
|
||||
"Star" -> DynamicDecorationResource.Star.serializer()
|
||||
else -> DynamicDecorationResource.Unknown.serializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-24
@@ -26,24 +26,21 @@ import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanEntitlement
|
||||
import me.proton.core.plan.domain.entity.DynamicEntitlement
|
||||
|
||||
@Serializable(EntitlementResourceSerializer::class)
|
||||
sealed class EntitlementResource {
|
||||
@Serializable(DynamicEntitlementResourceSerializer::class)
|
||||
sealed class DynamicEntitlementResource {
|
||||
@Serializable
|
||||
data class Description(
|
||||
@SerialName("Icon")
|
||||
val icon: String,
|
||||
|
||||
@SerialName("IconName")
|
||||
val iconName: String,
|
||||
|
||||
@SerialName("Text")
|
||||
val text: String,
|
||||
val text: String? = null, // TODO: Remove nullability.
|
||||
|
||||
@SerialName("Hint")
|
||||
val hint: String? = null
|
||||
) : EntitlementResource()
|
||||
) : DynamicEntitlementResource()
|
||||
|
||||
@Serializable
|
||||
data class Storage(
|
||||
@@ -52,39 +49,38 @@ sealed class EntitlementResource {
|
||||
|
||||
@SerialName("Max")
|
||||
val max: Long
|
||||
) : EntitlementResource()
|
||||
) : DynamicEntitlementResource()
|
||||
|
||||
@Serializable
|
||||
data class Unknown(
|
||||
@SerialName("Type")
|
||||
val type: String
|
||||
) : EntitlementResource()
|
||||
) : DynamicEntitlementResource()
|
||||
}
|
||||
|
||||
fun EntitlementResource.toDynamicPlanEntitlement(): DynamicPlanEntitlement? =
|
||||
fun DynamicEntitlementResource.toDynamicPlanEntitlement(iconsEndpoint: String): DynamicEntitlement? =
|
||||
when (this) {
|
||||
is EntitlementResource.Description -> DynamicPlanEntitlement.Description(
|
||||
is DynamicEntitlementResource.Description -> if (text == null) null else DynamicEntitlement.Description(
|
||||
text = text,
|
||||
iconBase64 = icon,
|
||||
iconName = iconName,
|
||||
iconUrl = "$iconsEndpoint/$iconName",
|
||||
hint = hint
|
||||
)
|
||||
|
||||
is EntitlementResource.Storage -> DynamicPlanEntitlement.Storage(
|
||||
currentMBytes = current,
|
||||
maxMBytes = max
|
||||
is DynamicEntitlementResource.Storage -> DynamicEntitlement.Storage(
|
||||
currentBytes = current,
|
||||
maxBytes = max
|
||||
)
|
||||
|
||||
is EntitlementResource.Unknown -> null
|
||||
is DynamicEntitlementResource.Unknown -> null
|
||||
}
|
||||
|
||||
class EntitlementResourceSerializer :
|
||||
JsonContentPolymorphicSerializer<EntitlementResource>(EntitlementResource::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out EntitlementResource> {
|
||||
class DynamicEntitlementResourceSerializer :
|
||||
JsonContentPolymorphicSerializer<DynamicEntitlementResource>(DynamicEntitlementResource::class) {
|
||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out DynamicEntitlementResource> {
|
||||
return when (element.jsonObject["Type"]?.jsonPrimitive?.contentOrNull) {
|
||||
"description" -> EntitlementResource.Description.serializer()
|
||||
"storage" -> EntitlementResource.Storage.serializer()
|
||||
else -> EntitlementResource.Unknown.serializer()
|
||||
"description" -> DynamicEntitlementResource.Description.serializer()
|
||||
"storage" -> DynamicEntitlementResource.Storage.serializer()
|
||||
else -> DynamicEntitlementResource.Unknown.serializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -28,8 +28,8 @@ internal data class DynamicPlanInstanceResource(
|
||||
@SerialName("ID")
|
||||
val id: String,
|
||||
|
||||
@SerialName("Months")
|
||||
val months: Int,
|
||||
@SerialName("Cycle")
|
||||
val cycle: Int,
|
||||
|
||||
@SerialName("Description")
|
||||
val description: String,
|
||||
@@ -47,9 +47,9 @@ internal data class DynamicPlanInstanceResource(
|
||||
internal fun DynamicPlanInstanceResource.toDynamicPlanInstance(): DynamicPlanInstance =
|
||||
DynamicPlanInstance(
|
||||
id = id,
|
||||
months = months,
|
||||
cycle = cycle,
|
||||
description = description,
|
||||
periodEnd = Instant.ofEpochSecond(periodEnd),
|
||||
price = price.map { it.toDynamicPlanPrice() },
|
||||
price = price.associate { it.currency to it.toDynamicPlanPrice() },
|
||||
vendors = vendors.toPlanVendorDataMap(),
|
||||
)
|
||||
|
||||
+5
-9
@@ -33,9 +33,6 @@ import java.util.EnumSet
|
||||
|
||||
@Serializable
|
||||
internal data class DynamicPlanResource(
|
||||
@SerialName("ID")
|
||||
val id: String,
|
||||
|
||||
@SerialName("Name")
|
||||
val name: String,
|
||||
|
||||
@@ -46,13 +43,13 @@ internal data class DynamicPlanResource(
|
||||
val title: String,
|
||||
|
||||
@SerialName("Decorations")
|
||||
val decorations: List<PlanDecorationResource> = emptyList(),
|
||||
val decorations: List<DynamicDecorationResource> = emptyList(),
|
||||
|
||||
@SerialName("Description")
|
||||
val description: String? = null,
|
||||
|
||||
@SerialName("Entitlements")
|
||||
val entitlements: List<EntitlementResource> = emptyList(),
|
||||
val entitlements: List<DynamicEntitlementResource> = emptyList(),
|
||||
|
||||
@SerialName("Features")
|
||||
val features: Int? = null,
|
||||
@@ -76,8 +73,7 @@ internal data class DynamicPlanResource(
|
||||
val type: Int? = null
|
||||
)
|
||||
|
||||
internal fun DynamicPlanResource.toDynamicPlan(order: Int): DynamicPlan = DynamicPlan(
|
||||
id = id,
|
||||
internal fun DynamicPlanResource.toDynamicPlan(iconsEndpoint: String, order: Int): DynamicPlan = DynamicPlan(
|
||||
name = name,
|
||||
order = order,
|
||||
state = when {
|
||||
@@ -85,13 +81,13 @@ internal fun DynamicPlanResource.toDynamicPlan(order: Int): DynamicPlan = Dynami
|
||||
else -> DynamicPlanState.Unavailable
|
||||
},
|
||||
title = title,
|
||||
entitlements = entitlements.mapNotNull { it.toDynamicPlanEntitlement() },
|
||||
entitlements = entitlements.mapNotNull { it.toDynamicPlanEntitlement(iconsEndpoint) },
|
||||
decorations = decorations.mapNotNull { it.toDynamicPlanDecoration() },
|
||||
description = description,
|
||||
features = features?.let { features ->
|
||||
EnumSet.copyOf(DynamicPlanFeature.values().filter { features.hasFlag(it.code) })
|
||||
} ?: EnumSet.noneOf(DynamicPlanFeature::class.java),
|
||||
instances = instances.map { it.toDynamicPlanInstance() },
|
||||
instances = instances.associate { it.cycle to it.toDynamicPlanInstance() },
|
||||
layout = layout?.let { layout ->
|
||||
StringEnum(layout, DynamicPlanLayout.values().firstOrNull { it.code == layout })
|
||||
} ?: StringEnum(DynamicPlanLayout.Default.code, DynamicPlanLayout.Default),
|
||||
|
||||
@@ -150,19 +150,22 @@ internal data class PlanVendorResponse(
|
||||
val plans: Map<Int, String>,
|
||||
|
||||
@SerialName("CustomerID")
|
||||
val customerId: String
|
||||
val customerId: String? = null
|
||||
)
|
||||
|
||||
internal fun Map<String, PlanVendorResponse>.toPlanVendorDataMap(): Map<AppStore, PlanVendorData> {
|
||||
return mapNotNull { entry ->
|
||||
when (entry.key) {
|
||||
PLAN_VENDOR_GOOGLE -> AppStore.GooglePlay
|
||||
else -> null
|
||||
}?.let { appStore ->
|
||||
appStore to PlanVendorData(
|
||||
customerId = entry.value.customerId,
|
||||
names = entry.value.plans.mapKeys { PlanDuration(it.key) }
|
||||
)
|
||||
when (val customerId = entry.value.customerId) {
|
||||
null -> null
|
||||
else -> when (entry.key) {
|
||||
PLAN_VENDOR_GOOGLE -> AppStore.GooglePlay
|
||||
else -> null
|
||||
}?.let { appStore ->
|
||||
appStore to PlanVendorData(
|
||||
customerId = customerId,
|
||||
names = entry.value.plans.mapKeys { PlanDuration(it.key) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
+18
-10
@@ -23,6 +23,7 @@ import me.proton.core.domain.entity.SessionUserId
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
import me.proton.core.plan.data.api.PlansApi
|
||||
import me.proton.core.plan.data.api.response.toDynamicPlan
|
||||
import me.proton.core.plan.domain.PlanIconsEndpointProvider
|
||||
import me.proton.core.plan.domain.entity.DynamicPlan
|
||||
import me.proton.core.plan.domain.entity.Plan
|
||||
import me.proton.core.plan.domain.repository.PlansRepository
|
||||
@@ -32,30 +33,37 @@ import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@Singleton
|
||||
class PlansRepositoryImpl @Inject constructor(
|
||||
private val provider: ApiProvider
|
||||
private val apiProvider: ApiProvider,
|
||||
private val endpointProvider: PlanIconsEndpointProvider
|
||||
) : PlansRepository {
|
||||
|
||||
private val dynamicPlansCache =
|
||||
Cache.Builder().expireAfterWrite(1.minutes).build<String, List<DynamicPlan>>()
|
||||
private val plansCache = Cache.Builder().expireAfterWrite(1.minutes).build<Unit, List<Plan>>()
|
||||
|
||||
override suspend fun getDynamicPlans(sessionUserId: SessionUserId?): List<DynamicPlan> =
|
||||
dynamicPlansCache.get(sessionUserId?.id ?: "") {
|
||||
provider.get<PlansApi>(sessionUserId).invoke {
|
||||
getDynamicPlans().plans.mapIndexed { index, resource -> resource.toDynamicPlan(index) }
|
||||
}.valueOrThrow
|
||||
}
|
||||
private val plansCache =
|
||||
Cache.Builder().expireAfterWrite(1.minutes).build<Unit, List<Plan>>()
|
||||
|
||||
override suspend fun getDynamicPlans(
|
||||
sessionUserId: SessionUserId?
|
||||
): List<DynamicPlan> = dynamicPlansCache.get(sessionUserId?.id ?: "") {
|
||||
apiProvider.get<PlansApi>(sessionUserId).invoke {
|
||||
getDynamicPlans().plans.mapIndexed { index, resource ->
|
||||
resource.toDynamicPlan(endpointProvider.get(), index)
|
||||
}
|
||||
}.valueOrThrow
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns from the API all plans available for the user in the moment.
|
||||
*/
|
||||
override suspend fun getPlans(sessionUserId: SessionUserId?) = plansCache.get(Unit) {
|
||||
provider.get<PlansApi>(sessionUserId).invoke {
|
||||
apiProvider.get<PlansApi>(sessionUserId).invoke {
|
||||
getPlans().plans.map { it.toPlan() }
|
||||
}.valueOrThrow
|
||||
}
|
||||
|
||||
override suspend fun getPlansDefault(sessionUserId: SessionUserId?): Plan =
|
||||
provider.get<PlansApi>(sessionUserId).invoke {
|
||||
apiProvider.get<PlansApi>(sessionUserId).invoke {
|
||||
getPlansDefault().plan.toPlan()
|
||||
}.valueOrThrow
|
||||
}
|
||||
|
||||
+10
-28
@@ -18,64 +18,46 @@
|
||||
|
||||
package me.proton.core.plan.data.api.response
|
||||
|
||||
import android.util.Base64
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanDecoration
|
||||
import me.proton.core.plan.domain.entity.DynamicDecoration
|
||||
import me.proton.core.util.kotlin.deserialize
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class PlanDecorationResourceTest {
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
mockkStatic(Base64::class)
|
||||
every { Base64.decode(any<String>(), any()) } answers {
|
||||
firstArg<String>().toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun tearDown() {
|
||||
unmockkStatic(Base64::class)
|
||||
}
|
||||
class DynamicDecorationResourceTest {
|
||||
|
||||
@Test
|
||||
fun fromJsonToResource() {
|
||||
assertEquals(
|
||||
PlanDecorationResource.Star(icon = "icon"),
|
||||
DynamicDecorationResource.Star(iconName = "icon"),
|
||||
"""
|
||||
{
|
||||
"Type": "Star",
|
||||
"Icon": "icon"
|
||||
"IconName": "icon"
|
||||
}
|
||||
""".trimIndent().deserialize<PlanDecorationResource>()
|
||||
""".trimIndent().deserialize<DynamicDecorationResource>()
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
PlanDecorationResource.Unknown(type = "custom"),
|
||||
DynamicDecorationResource.Unknown(type = "custom"),
|
||||
"""
|
||||
{
|
||||
"Type": "custom",
|
||||
"Color": "red"
|
||||
}
|
||||
""".trimIndent().deserialize<PlanDecorationResource>()
|
||||
""".trimIndent().deserialize<DynamicDecorationResource>()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromResourceToDomain() {
|
||||
assertEquals(
|
||||
DynamicPlanDecoration.Star(iconBase64 = "icon"),
|
||||
PlanDecorationResource.Star(icon = "icon").toDynamicPlanDecoration()
|
||||
DynamicDecoration.Star(iconName = "icon"),
|
||||
DynamicDecorationResource.Star(iconName = "icon").toDynamicPlanDecoration()
|
||||
)
|
||||
|
||||
assertNull(
|
||||
PlanDecorationResource.Unknown(type = "custom").toDynamicPlanDecoration()
|
||||
DynamicDecorationResource.Unknown(type = "custom").toDynamicPlanDecoration()
|
||||
)
|
||||
}
|
||||
}
|
||||
+18
-21
@@ -22,7 +22,7 @@ import android.util.Base64
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanEntitlement
|
||||
import me.proton.core.plan.domain.entity.DynamicEntitlement
|
||||
import me.proton.core.util.kotlin.deserialize
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.BeforeTest
|
||||
@@ -30,7 +30,7 @@ import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class EntitlementResourceTest {
|
||||
class DynamicEntitlementResourceTest {
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
mockkStatic(Base64::class)
|
||||
@@ -47,8 +47,7 @@ class EntitlementResourceTest {
|
||||
@Test
|
||||
fun fromJsonToResource() {
|
||||
assertEquals(
|
||||
EntitlementResource.Description(
|
||||
icon = "icon",
|
||||
DynamicEntitlementResource.Description(
|
||||
iconName = "icon-name",
|
||||
text = "text",
|
||||
hint = "hint"
|
||||
@@ -61,11 +60,11 @@ class EntitlementResourceTest {
|
||||
"Type": "description",
|
||||
"Hint": "hint"
|
||||
}
|
||||
""".trimIndent().deserialize<EntitlementResource>()
|
||||
""".trimIndent().deserialize<DynamicEntitlementResource>()
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
EntitlementResource.Storage(
|
||||
DynamicEntitlementResource.Storage(
|
||||
current = 128,
|
||||
max = 1024
|
||||
),
|
||||
@@ -75,14 +74,14 @@ class EntitlementResourceTest {
|
||||
"Max": 1024,
|
||||
"Type": "storage"
|
||||
}
|
||||
""".trimIndent().deserialize<EntitlementResource>()
|
||||
""".trimIndent().deserialize<DynamicEntitlementResource>()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unknownEntitlementType() {
|
||||
assertEquals(
|
||||
EntitlementResource.Unknown(
|
||||
DynamicEntitlementResource.Unknown(
|
||||
type = "custom"
|
||||
),
|
||||
"""
|
||||
@@ -92,40 +91,38 @@ class EntitlementResourceTest {
|
||||
"Text": "text",
|
||||
"Type": "custom"
|
||||
}
|
||||
""".trimIndent().deserialize<EntitlementResource>()
|
||||
""".trimIndent().deserialize<DynamicEntitlementResource>()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromResourceToDomain() {
|
||||
assertEquals(
|
||||
DynamicPlanEntitlement.Description(
|
||||
iconBase64 = "icon",
|
||||
iconName = "tick",
|
||||
DynamicEntitlement.Description(
|
||||
iconUrl = "endpoint/tick",
|
||||
text = "Entitlements text",
|
||||
hint = "Entitlements hint"
|
||||
),
|
||||
EntitlementResource.Description(
|
||||
icon = "icon",
|
||||
DynamicEntitlementResource.Description(
|
||||
iconName = "tick",
|
||||
text = "Entitlements text",
|
||||
hint = "Entitlements hint"
|
||||
).toDynamicPlanEntitlement()
|
||||
).toDynamicPlanEntitlement("endpoint")
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DynamicPlanEntitlement.Storage(
|
||||
currentMBytes = 128,
|
||||
maxMBytes = 1024
|
||||
DynamicEntitlement.Storage(
|
||||
currentBytes = 128,
|
||||
maxBytes = 1024
|
||||
),
|
||||
EntitlementResource.Storage(
|
||||
DynamicEntitlementResource.Storage(
|
||||
current = 128,
|
||||
max = 1024
|
||||
).toDynamicPlanEntitlement()
|
||||
).toDynamicPlanEntitlement("endpoint")
|
||||
)
|
||||
|
||||
assertNull(
|
||||
EntitlementResource.Unknown("custom").toDynamicPlanEntitlement()
|
||||
DynamicEntitlementResource.Unknown("custom").toDynamicPlanEntitlement("endpoint")
|
||||
)
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -30,7 +30,7 @@ class DynamicPlanInstanceResourceTest {
|
||||
assertEquals(
|
||||
DynamicPlanInstanceResource(
|
||||
id = "123abc",
|
||||
months = 1,
|
||||
cycle = 1,
|
||||
description = "description",
|
||||
periodEnd = 100,
|
||||
price = emptyList(),
|
||||
@@ -39,7 +39,7 @@ class DynamicPlanInstanceResourceTest {
|
||||
"""
|
||||
{
|
||||
"ID": "123abc",
|
||||
"Months": 1,
|
||||
"Cycle": 1,
|
||||
"Description": "description",
|
||||
"PeriodEnd": 100,
|
||||
"Price": [],
|
||||
@@ -54,15 +54,15 @@ class DynamicPlanInstanceResourceTest {
|
||||
assertEquals(
|
||||
DynamicPlanInstance(
|
||||
id = "123abc",
|
||||
months = 1,
|
||||
cycle = 1,
|
||||
description = "description",
|
||||
periodEnd = Instant.ofEpochSecond(100),
|
||||
price = emptyList(),
|
||||
price = emptyMap(),
|
||||
vendors = emptyMap()
|
||||
),
|
||||
DynamicPlanInstanceResource(
|
||||
id = "123abc",
|
||||
months = 1,
|
||||
cycle = 1,
|
||||
description = "description",
|
||||
periodEnd = 100,
|
||||
price = emptyList(),
|
||||
|
||||
+2
-5
@@ -36,7 +36,6 @@ class DynamicPlanResourceTest {
|
||||
fun fromJsonToResource() {
|
||||
assertEquals(
|
||||
DynamicPlanResource(
|
||||
id = "123abc",
|
||||
name = "name",
|
||||
state = 1,
|
||||
title = "title",
|
||||
@@ -76,7 +75,6 @@ class DynamicPlanResourceTest {
|
||||
fun fromResourceToDomain() {
|
||||
assertEquals(
|
||||
DynamicPlan(
|
||||
id = "123abc",
|
||||
name = "name",
|
||||
order = 5,
|
||||
state = DynamicPlanState.Available,
|
||||
@@ -85,7 +83,7 @@ class DynamicPlanResourceTest {
|
||||
decorations = emptyList(),
|
||||
description = "description",
|
||||
features = EnumSet.of(DynamicPlanFeature.CatchAll),
|
||||
instances = emptyList(),
|
||||
instances = emptyMap(),
|
||||
layout = StringEnum("default", DynamicPlanLayout.Default),
|
||||
offers = emptyList(),
|
||||
parentMetaPlanID = "parentId",
|
||||
@@ -93,7 +91,6 @@ class DynamicPlanResourceTest {
|
||||
type = IntEnum(DynamicPlanType.Primary.code, DynamicPlanType.Primary)
|
||||
),
|
||||
DynamicPlanResource(
|
||||
id = "123abc",
|
||||
name = "name",
|
||||
state = 1,
|
||||
title = "title",
|
||||
@@ -107,7 +104,7 @@ class DynamicPlanResourceTest {
|
||||
parentMetaPlanID = "parentId",
|
||||
services = 15,
|
||||
type = 1
|
||||
).toDynamicPlan(5)
|
||||
).toDynamicPlan("endpoint", 5)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -33,6 +33,7 @@ import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.network.domain.session.SessionId
|
||||
import me.proton.core.network.domain.session.SessionProvider
|
||||
import me.proton.core.plan.data.api.PlansApi
|
||||
import me.proton.core.plan.domain.PlanIconsEndpointProvider
|
||||
import me.proton.core.plan.domain.entity.DynamicPlan
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanState
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanType
|
||||
@@ -51,6 +52,9 @@ import kotlin.test.assertNull
|
||||
|
||||
class PlansRepositoryImplTest {
|
||||
// region mocks
|
||||
private val endpointProvider = mockk<PlanIconsEndpointProvider> {
|
||||
every { get() } returns "endpoint"
|
||||
}
|
||||
private val sessionProvider = mockk<SessionProvider>(relaxed = true)
|
||||
private val apiFactory = mockk<ApiManagerFactory>(relaxed = true)
|
||||
private val apiManager = mockk<ApiManager<PlansApi>>(relaxed = true)
|
||||
@@ -81,7 +85,7 @@ class PlansRepositoryImplTest {
|
||||
interfaceClass = PlansApi::class
|
||||
)
|
||||
} returns apiManager
|
||||
repository = PlansRepositoryImpl(apiProvider)
|
||||
repository = PlansRepositoryImpl(apiProvider, endpointProvider)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -296,7 +300,6 @@ class PlansRepositoryImplTest {
|
||||
fun `get dynamic plans`() = runTest(dispatcherProvider.Main) {
|
||||
// GIVEN
|
||||
val plan = DynamicPlan(
|
||||
id = "id",
|
||||
name = "name",
|
||||
order = 0,
|
||||
state = DynamicPlanState.Available,
|
||||
|
||||
@@ -8,6 +8,10 @@ public abstract interface class me/proton/core/plan/domain/IsDynamicPlanEnabled
|
||||
public abstract fun isRemoteEnabled (Lme/proton/core/domain/entity/UserId;)Z
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/plan/domain/PlanIconsEndpointProvider {
|
||||
public abstract fun get ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface annotation class me/proton/core/plan/domain/ProductOnlyPaidPlans : java/lang/annotation/Annotation {
|
||||
}
|
||||
|
||||
@@ -17,33 +21,77 @@ public abstract interface annotation class me/proton/core/plan/domain/SupportSig
|
||||
public abstract interface annotation class me/proton/core/plan/domain/SupportUpgradePaidPlans : java/lang/annotation/Annotation {
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicPlan {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;ILme/proton/core/plan/domain/entity/DynamicPlanState;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Ljava/util/EnumSet;Ljava/util/List;Lme/proton/core/domain/type/StringEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/EnumSet;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ILme/proton/core/plan/domain/entity/DynamicPlanState;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Ljava/util/EnumSet;Ljava/util/List;Lme/proton/core/domain/type/StringEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/EnumSet;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public abstract class me/proton/core/plan/domain/entity/DynamicDecoration {
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicDecoration$Star : me/proton/core/plan/domain/entity/DynamicDecoration {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;)Lme/proton/core/plan/domain/entity/DynamicDecoration$Star;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicDecoration$Star;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicDecoration$Star;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getIconName ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/domain/entity/DynamicEntitlement {
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicEntitlement$Description : me/proton/core/plan/domain/entity/DynamicEntitlement {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun component10 ()Ljava/util/EnumSet;
|
||||
public final fun component11 ()Ljava/util/List;
|
||||
public final fun component12 ()Lme/proton/core/domain/type/StringEnum;
|
||||
public final fun component13 ()Ljava/util/List;
|
||||
public final fun component14 ()Ljava/lang/String;
|
||||
public final fun component15 ()Ljava/util/EnumSet;
|
||||
public final fun component2 ()Ljava/lang/String;
|
||||
public final fun component3 ()I
|
||||
public final fun component4 ()Lme/proton/core/plan/domain/entity/DynamicPlanState;
|
||||
public final fun component5 ()Ljava/lang/String;
|
||||
public final fun component6 ()Lme/proton/core/domain/type/IntEnum;
|
||||
public final fun component7 ()Ljava/util/List;
|
||||
public final fun component8 ()Ljava/lang/String;
|
||||
public final fun component9 ()Ljava/util/List;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;ILme/proton/core/plan/domain/entity/DynamicPlanState;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Ljava/util/EnumSet;Ljava/util/List;Lme/proton/core/domain/type/StringEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/EnumSet;)Lme/proton/core/plan/domain/entity/DynamicPlan;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicPlan;Ljava/lang/String;Ljava/lang/String;ILme/proton/core/plan/domain/entity/DynamicPlanState;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Ljava/util/EnumSet;Ljava/util/List;Lme/proton/core/domain/type/StringEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/EnumSet;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicPlan;
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lme/proton/core/plan/domain/entity/DynamicEntitlement$Description;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicEntitlement$Description;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicEntitlement$Description;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getHint ()Ljava/lang/String;
|
||||
public final fun getIconUrl ()Ljava/lang/String;
|
||||
public final fun getText ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicEntitlement$Storage : me/proton/core/plan/domain/entity/DynamicEntitlement {
|
||||
public fun <init> (JJ)V
|
||||
public final fun component1 ()J
|
||||
public final fun component2 ()J
|
||||
public final fun copy (JJ)Lme/proton/core/plan/domain/entity/DynamicEntitlement$Storage;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicEntitlement$Storage;JJILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicEntitlement$Storage;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getCurrentBytes ()J
|
||||
public final fun getMaxBytes ()J
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicPlan {
|
||||
public fun <init> (Ljava/lang/String;ILme/proton/core/plan/domain/entity/DynamicPlanState;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Ljava/util/EnumSet;Ljava/util/Map;Lme/proton/core/domain/type/StringEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/EnumSet;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;ILme/proton/core/plan/domain/entity/DynamicPlanState;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Ljava/util/EnumSet;Ljava/util/Map;Lme/proton/core/domain/type/StringEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/EnumSet;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun component10 ()Ljava/util/Map;
|
||||
public final fun component11 ()Lme/proton/core/domain/type/StringEnum;
|
||||
public final fun component12 ()Ljava/util/List;
|
||||
public final fun component13 ()Ljava/lang/String;
|
||||
public final fun component14 ()Ljava/util/EnumSet;
|
||||
public final fun component2 ()I
|
||||
public final fun component3 ()Lme/proton/core/plan/domain/entity/DynamicPlanState;
|
||||
public final fun component4 ()Ljava/lang/String;
|
||||
public final fun component5 ()Lme/proton/core/domain/type/IntEnum;
|
||||
public final fun component6 ()Ljava/util/List;
|
||||
public final fun component7 ()Ljava/lang/String;
|
||||
public final fun component8 ()Ljava/util/List;
|
||||
public final fun component9 ()Ljava/util/EnumSet;
|
||||
public final fun copy (Ljava/lang/String;ILme/proton/core/plan/domain/entity/DynamicPlanState;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Ljava/util/EnumSet;Ljava/util/Map;Lme/proton/core/domain/type/StringEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/EnumSet;)Lme/proton/core/plan/domain/entity/DynamicPlan;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicPlan;Ljava/lang/String;ILme/proton/core/plan/domain/entity/DynamicPlanState;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/List;Ljava/util/EnumSet;Ljava/util/Map;Lme/proton/core/domain/type/StringEnum;Ljava/util/List;Ljava/lang/String;Ljava/util/EnumSet;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicPlan;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getDecorations ()Ljava/util/List;
|
||||
public final fun getDescription ()Ljava/lang/String;
|
||||
public final fun getEntitlements ()Ljava/util/List;
|
||||
public final fun getFeatures ()Ljava/util/EnumSet;
|
||||
public final fun getId ()Ljava/lang/String;
|
||||
public final fun getInstances ()Ljava/util/List;
|
||||
public final fun getInstances ()Ljava/util/Map;
|
||||
public final fun getLayout ()Lme/proton/core/domain/type/StringEnum;
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
public final fun getOffers ()Ljava/util/List;
|
||||
@@ -57,54 +105,6 @@ public final class me/proton/core/plan/domain/entity/DynamicPlan {
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/domain/entity/DynamicPlanDecoration {
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicPlanDecoration$Star : me/proton/core/plan/domain/entity/DynamicPlanDecoration {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;)Lme/proton/core/plan/domain/entity/DynamicPlanDecoration$Star;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicPlanDecoration$Star;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicPlanDecoration$Star;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getIconBase64 ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/domain/entity/DynamicPlanEntitlement {
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicPlanEntitlement$Description : me/proton/core/plan/domain/entity/DynamicPlanEntitlement {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun component2 ()Ljava/lang/String;
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun component4 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lme/proton/core/plan/domain/entity/DynamicPlanEntitlement$Description;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicPlanEntitlement$Description;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicPlanEntitlement$Description;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getHint ()Ljava/lang/String;
|
||||
public final fun getIconBase64 ()Ljava/lang/String;
|
||||
public final fun getIconName ()Ljava/lang/String;
|
||||
public final fun getText ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicPlanEntitlement$Storage : me/proton/core/plan/domain/entity/DynamicPlanEntitlement {
|
||||
public fun <init> (JJ)V
|
||||
public final fun component1 ()J
|
||||
public final fun component2 ()J
|
||||
public final fun copy (JJ)Lme/proton/core/plan/domain/entity/DynamicPlanEntitlement$Storage;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicPlanEntitlement$Storage;JJILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicPlanEntitlement$Storage;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getCurrentMBytes ()J
|
||||
public final fun getMaxMBytes ()J
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicPlanFeature : java/lang/Enum {
|
||||
public static final field CatchAll Lme/proton/core/plan/domain/entity/DynamicPlanFeature;
|
||||
public final fun getCode ()I
|
||||
@@ -113,28 +113,29 @@ public final class me/proton/core/plan/domain/entity/DynamicPlanFeature : java/l
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicPlanInstance {
|
||||
public fun <init> (Ljava/lang/String;ILjava/lang/String;Ljava/time/Instant;Ljava/util/List;Ljava/util/Map;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;ILjava/lang/String;Ljava/time/Instant;Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;ILjava/lang/String;Ljava/time/Instant;Ljava/util/Map;Ljava/util/Map;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;ILjava/lang/String;Ljava/time/Instant;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun component2 ()I
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun component4 ()Ljava/time/Instant;
|
||||
public final fun component5 ()Ljava/util/List;
|
||||
public final fun component5 ()Ljava/util/Map;
|
||||
public final fun component6 ()Ljava/util/Map;
|
||||
public final fun copy (Ljava/lang/String;ILjava/lang/String;Ljava/time/Instant;Ljava/util/List;Ljava/util/Map;)Lme/proton/core/plan/domain/entity/DynamicPlanInstance;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicPlanInstance;Ljava/lang/String;ILjava/lang/String;Ljava/time/Instant;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicPlanInstance;
|
||||
public final fun copy (Ljava/lang/String;ILjava/lang/String;Ljava/time/Instant;Ljava/util/Map;Ljava/util/Map;)Lme/proton/core/plan/domain/entity/DynamicPlanInstance;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicPlanInstance;Ljava/lang/String;ILjava/lang/String;Ljava/time/Instant;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicPlanInstance;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getCycle ()I
|
||||
public final fun getDescription ()Ljava/lang/String;
|
||||
public final fun getId ()Ljava/lang/String;
|
||||
public final fun getMonths ()I
|
||||
public final fun getPeriodEnd ()Ljava/time/Instant;
|
||||
public final fun getPrice ()Ljava/util/List;
|
||||
public final fun getPrice ()Ljava/util/Map;
|
||||
public final fun getVendors ()Ljava/util/Map;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/domain/entity/DynamicPlanKt {
|
||||
public static final fun filterBy (Ljava/util/List;ILjava/lang/String;)Ljava/util/List;
|
||||
public static final fun hasServiceFor (Lme/proton/core/plan/domain/entity/DynamicPlan;Lme/proton/core/domain/entity/Product;Z)Z
|
||||
public static final fun isFree (Lme/proton/core/plan/domain/entity/DynamicPlan;)Z
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.domain
|
||||
|
||||
fun interface PlanIconsEndpointProvider {
|
||||
/**
|
||||
* Provide the endpoint for fetching icon resources.
|
||||
*
|
||||
* Example: "https://api.{baseApiUrl}/payments/v5/resources/icons/"
|
||||
*/
|
||||
fun get(): String
|
||||
}
|
||||
+2
-2
@@ -18,6 +18,6 @@
|
||||
|
||||
package me.proton.core.plan.domain.entity
|
||||
|
||||
sealed class DynamicPlanDecoration {
|
||||
data class Star(val iconBase64: String) : DynamicPlanDecoration()
|
||||
sealed class DynamicDecoration {
|
||||
data class Star(val iconName: String) : DynamicDecoration()
|
||||
}
|
||||
+6
-7
@@ -18,16 +18,15 @@
|
||||
|
||||
package me.proton.core.plan.domain.entity
|
||||
|
||||
sealed class DynamicPlanEntitlement {
|
||||
sealed class DynamicEntitlement {
|
||||
data class Description(
|
||||
val text: String,
|
||||
val iconBase64: String,
|
||||
val iconName: String,
|
||||
val iconUrl: String,
|
||||
val hint: String? = null,
|
||||
) : DynamicPlanEntitlement()
|
||||
) : DynamicEntitlement()
|
||||
|
||||
data class Storage(
|
||||
val currentMBytes: Long,
|
||||
val maxMBytes: Long
|
||||
) : DynamicPlanEntitlement()
|
||||
val currentBytes: Long,
|
||||
val maxBytes: Long
|
||||
) : DynamicEntitlement()
|
||||
}
|
||||
@@ -24,27 +24,27 @@ import me.proton.core.domain.type.StringEnum
|
||||
import java.util.EnumSet
|
||||
|
||||
data class DynamicPlan(
|
||||
val id: String,
|
||||
val name: String, // code name
|
||||
val order: Int,
|
||||
val state: DynamicPlanState,
|
||||
val title: String,
|
||||
val type: IntEnum<DynamicPlanType>?,
|
||||
|
||||
val decorations: List<DynamicPlanDecoration> = emptyList(),
|
||||
val decorations: List<DynamicDecoration> = emptyList(),
|
||||
val description: String? = null,
|
||||
val entitlements: List<DynamicPlanEntitlement> = emptyList(),
|
||||
val entitlements: List<DynamicEntitlement> = emptyList(),
|
||||
val features: EnumSet<DynamicPlanFeature> = EnumSet.noneOf(DynamicPlanFeature::class.java),
|
||||
val instances: List<DynamicPlanInstance> = emptyList(),
|
||||
val layout: StringEnum<DynamicPlanLayout> = StringEnum(
|
||||
DynamicPlanLayout.Default.code,
|
||||
DynamicPlanLayout.Default
|
||||
),
|
||||
/** Map<Cycle, DynamicPlanInstance> */
|
||||
val instances: Map<Int, DynamicPlanInstance> = emptyMap(),
|
||||
val layout: StringEnum<DynamicPlanLayout> = StringEnum(DynamicPlanLayout.Default.code, DynamicPlanLayout.Default),
|
||||
val offers: List<DynamicPlanOffer> = emptyList(),
|
||||
val parentMetaPlanID: String? = null,
|
||||
val services: EnumSet<DynamicPlanService> = EnumSet.noneOf(DynamicPlanService::class.java)
|
||||
)
|
||||
|
||||
fun List<DynamicPlan>.filterBy(cycle: Int, currency: String?) =
|
||||
filter { it.instances[cycle]?.price?.containsKey(currency) ?: true }
|
||||
|
||||
fun DynamicPlan.hasServiceFor(product: Product, exclusive: Boolean): Boolean {
|
||||
val service = when (product) {
|
||||
Product.Calendar -> DynamicPlanService.Calendar
|
||||
|
||||
+3
-2
@@ -23,9 +23,10 @@ import java.time.Instant
|
||||
|
||||
data class DynamicPlanInstance(
|
||||
val id: String,
|
||||
val months: Int,
|
||||
val cycle: Int,
|
||||
val description: String,
|
||||
val periodEnd: Instant,
|
||||
val price: List<DynamicPlanPrice>,
|
||||
/** Map<Currency, DynamicPlanPrice> */
|
||||
val price: Map<String, DynamicPlanPrice>,
|
||||
val vendors: Map<AppStore, PlanVendorData> = emptyMap()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.domain.entity
|
||||
|
||||
import java.time.Instant
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DynamicPlanTest {
|
||||
|
||||
private fun instanceFor(cycle: Int, vararg currency: String) = DynamicPlanInstance(
|
||||
id = "id-$cycle-$currency",
|
||||
cycle = cycle,
|
||||
description = "description-$cycle-$currency",
|
||||
periodEnd = Instant.now(),
|
||||
price = currency.map {
|
||||
DynamicPlanPrice(
|
||||
currency = it,
|
||||
current = 100
|
||||
)
|
||||
}.associateBy { it.currency },
|
||||
vendors = emptyMap()
|
||||
)
|
||||
|
||||
private val planEmpty = DynamicPlan(
|
||||
name = "empty",
|
||||
order = 0,
|
||||
state = DynamicPlanState.Available,
|
||||
title = "title",
|
||||
type = null,
|
||||
instances = emptyMap(), // No instances.
|
||||
)
|
||||
|
||||
private val plan1 = DynamicPlan(
|
||||
name = "test1",
|
||||
order = 0,
|
||||
state = DynamicPlanState.Available,
|
||||
title = "title",
|
||||
type = null,
|
||||
instances = mapOf(
|
||||
12 to instanceFor(12, "CHF", "USD", "EUR"),
|
||||
24 to instanceFor(24, "CHF")
|
||||
),
|
||||
)
|
||||
|
||||
private val plan2 = DynamicPlan(
|
||||
name = "test2",
|
||||
order = 0,
|
||||
state = DynamicPlanState.Available,
|
||||
title = "title2",
|
||||
type = null,
|
||||
instances = mapOf(
|
||||
12 to instanceFor(12, "CHF"),
|
||||
24 to instanceFor(24, "CHF", "USD", "EUR")
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun filterByAlwaysReturnPlanEmpty() {
|
||||
// Given
|
||||
val plans = listOf(planEmpty, plan1, plan2)
|
||||
|
||||
// When
|
||||
val filteredPlans = plans.filterBy(12, "CHF")
|
||||
|
||||
// Then
|
||||
assertEquals(3, filteredPlans.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterBy12CycleAndCHFCurrency() {
|
||||
// Given
|
||||
val plans = listOf(plan1, plan2)
|
||||
|
||||
// When
|
||||
val filteredPlans = plans.filterBy(12, "CHF")
|
||||
|
||||
// Then
|
||||
assertEquals(2, filteredPlans.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterBy12CycleAndEURCurrency() {
|
||||
// Given
|
||||
val plans = listOf(plan1, plan2)
|
||||
|
||||
// When
|
||||
val filteredPlans = plans.filterBy(12, "EUR")
|
||||
|
||||
// Then
|
||||
assertEquals(1, filteredPlans.size)
|
||||
assertEquals("test1", filteredPlans[0].name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterBy24CycleAndUSDCurrency() {
|
||||
// Given
|
||||
val plans = listOf(plan1, plan2)
|
||||
|
||||
// When
|
||||
val filteredPlans = plans.filterBy(24, "USD")
|
||||
|
||||
// Then
|
||||
assertEquals(1, filteredPlans.size)
|
||||
assertEquals("test2", filteredPlans[0].name)
|
||||
}
|
||||
}
|
||||
@@ -19,21 +19,10 @@
|
||||
package me.proton.core.plan.domain.entity
|
||||
|
||||
import me.proton.core.domain.type.IntEnum
|
||||
import java.util.Base64
|
||||
import java.util.Calendar
|
||||
import java.util.EnumSet
|
||||
|
||||
private const val PLAN_ICON_SVG = """
|
||||
<svg width="204" height="204" xmlns="http://www.w3.org/2000/svg">
|
||||
<g><ellipse stroke-width="4" stroke="#000" ry="100" rx="100" id="svg_1" cy="102" cx="102" fill="#fff"/></g>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
private val planIconBase64 =
|
||||
Base64.getEncoder().encode(PLAN_ICON_SVG.toByteArray()).decodeToString()
|
||||
|
||||
val freePlan = DynamicPlan(
|
||||
id = "PRoqzg34",
|
||||
name = "free",
|
||||
order = 10,
|
||||
state = DynamicPlanState.Unavailable,
|
||||
@@ -41,9 +30,8 @@ val freePlan = DynamicPlan(
|
||||
type = null,
|
||||
description = "The no-cost starter account designed to empower everyone with privacy by default.",
|
||||
entitlements = listOf(
|
||||
DynamicPlanEntitlement.Description(
|
||||
iconBase64 = planIconBase64,
|
||||
iconName = "tick",
|
||||
DynamicEntitlement.Description(
|
||||
iconUrl = "tick",
|
||||
text = "Up to 1 GB storage",
|
||||
hint = "Start with 500 MB and unlock more storage along the way."
|
||||
)
|
||||
@@ -51,7 +39,6 @@ val freePlan = DynamicPlan(
|
||||
)
|
||||
|
||||
val mailPlusPlan = DynamicPlan(
|
||||
id = "l8vWAXHBQmv",
|
||||
name = "mail2022",
|
||||
order = 5,
|
||||
state = DynamicPlanState.Available,
|
||||
@@ -62,25 +49,21 @@ val mailPlusPlan = DynamicPlan(
|
||||
)
|
||||
|
||||
val unlimitedPlan = DynamicPlan(
|
||||
id = "lY2ZCYkVNfl_osze70PRoqzg34MQI64mE3-pLc-yMp_6KXthkV1paUsyS276OdNwucz9zKoWKZL_TgtKxOPb0w==",
|
||||
name = "bundle2022",
|
||||
order = 0,
|
||||
state = DynamicPlanState.Available,
|
||||
title = "Proton Unlimited",
|
||||
type = IntEnum(DynamicPlanType.Primary.code, DynamicPlanType.Primary),
|
||||
|
||||
decorations = listOf(DynamicPlanDecoration.Star(planIconBase64)),
|
||||
decorations = listOf(DynamicDecoration.Star("tick")),
|
||||
description = null,
|
||||
entitlements = listOf(
|
||||
DynamicPlanEntitlement.Description(
|
||||
iconBase64 = planIconBase64,
|
||||
iconName = "tick",
|
||||
DynamicEntitlement.Description(
|
||||
iconUrl = "tick",
|
||||
text = "500 GB storage",
|
||||
hint = "Storage space is shared across Proton Mail, Proton Calendar, and Proton Drive."
|
||||
),
|
||||
DynamicPlanEntitlement.Description(
|
||||
iconBase64 = planIconBase64,
|
||||
iconName = "tick",
|
||||
DynamicEntitlement.Description(
|
||||
iconUrl = "tick",
|
||||
text = "Unlimited folders, labels, and filters."
|
||||
)
|
||||
),
|
||||
@@ -88,7 +71,7 @@ val unlimitedPlan = DynamicPlan(
|
||||
instances = listOf(
|
||||
DynamicPlanInstance(
|
||||
id = "aJZHNZE_fd_rWygalcsahpc8ihMinUHUFGWXq8K0eoGH72CbCXp2KS82uvTPwdOw04ufbLk8zDyRDj7oNPrQCA==",
|
||||
months = 1,
|
||||
cycle = 1,
|
||||
description = "For 1 month",
|
||||
periodEnd = Calendar.getInstance().let {
|
||||
it.add(Calendar.MONTH, 1)
|
||||
@@ -100,9 +83,9 @@ val unlimitedPlan = DynamicPlan(
|
||||
current = 499,
|
||||
default = 499
|
||||
)
|
||||
)
|
||||
).associateBy { it.currency }
|
||||
)
|
||||
),
|
||||
).associateBy { it.cycle },
|
||||
offers = listOf(
|
||||
DynamicPlanOffer(
|
||||
name = "Next month's offer",
|
||||
|
||||
@@ -2,6 +2,10 @@ public class hilt_aggregated_deps/_me_proton_core_plan_presentation_HiltWrapper_
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public class hilt_aggregated_deps/_me_proton_core_plan_presentation_ui_DynamicPlanListFragment_GeneratedInjector {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public class hilt_aggregated_deps/_me_proton_core_plan_presentation_ui_DynamicSubscriptionFragment_GeneratedInjector {
|
||||
public fun <init> ()V
|
||||
}
|
||||
@@ -22,11 +26,11 @@ public class hilt_aggregated_deps/_me_proton_core_plan_presentation_ui_UpgradePl
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public class hilt_aggregated_deps/_me_proton_core_plan_presentation_viewmodel_DynamicPlansViewModel_HiltModules_BindsModule {
|
||||
public class hilt_aggregated_deps/_me_proton_core_plan_presentation_viewmodel_DynamicPlanListViewModel_HiltModules_BindsModule {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public class hilt_aggregated_deps/_me_proton_core_plan_presentation_viewmodel_DynamicPlansViewModel_HiltModules_KeyModule {
|
||||
public class hilt_aggregated_deps/_me_proton_core_plan_presentation_viewmodel_DynamicPlanListViewModel_HiltModules_KeyModule {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
@@ -128,20 +132,28 @@ public final class me/proton/core/plan/presentation/databinding/ConnectivityIssu
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;)Lme/proton/core/plan/presentation/databinding/ConnectivityIssueViewBinding;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/databinding/DynamicPlanEntitlementDescriptionViewBinding : androidx/viewbinding/ViewBinding {
|
||||
public final class me/proton/core/plan/presentation/databinding/DynamicEntitlementDescriptionViewBinding : androidx/viewbinding/ViewBinding {
|
||||
public final field icon Landroidx/appcompat/widget/AppCompatImageView;
|
||||
public final field text Landroid/widget/TextView;
|
||||
public static fun bind (Landroid/view/View;)Lme/proton/core/plan/presentation/databinding/DynamicPlanEntitlementDescriptionViewBinding;
|
||||
public static fun bind (Landroid/view/View;)Lme/proton/core/plan/presentation/databinding/DynamicEntitlementDescriptionViewBinding;
|
||||
public fun getRoot ()Landroid/view/View;
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;)Lme/proton/core/plan/presentation/databinding/DynamicPlanEntitlementDescriptionViewBinding;
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;)Lme/proton/core/plan/presentation/databinding/DynamicEntitlementDescriptionViewBinding;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/databinding/DynamicPlanEntitlementStorageViewBinding : androidx/viewbinding/ViewBinding {
|
||||
public final class me/proton/core/plan/presentation/databinding/DynamicEntitlementStorageViewBinding : androidx/viewbinding/ViewBinding {
|
||||
public final field progress Lcom/google/android/material/progressindicator/LinearProgressIndicator;
|
||||
public final field text Landroid/widget/TextView;
|
||||
public static fun bind (Landroid/view/View;)Lme/proton/core/plan/presentation/databinding/DynamicPlanEntitlementStorageViewBinding;
|
||||
public static fun bind (Landroid/view/View;)Lme/proton/core/plan/presentation/databinding/DynamicEntitlementStorageViewBinding;
|
||||
public fun getRoot ()Landroid/view/View;
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;)Lme/proton/core/plan/presentation/databinding/DynamicPlanEntitlementStorageViewBinding;
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;)Lme/proton/core/plan/presentation/databinding/DynamicEntitlementStorageViewBinding;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/databinding/DynamicPlanCardviewBinding : androidx/viewbinding/ViewBinding {
|
||||
public final field cardView Lcom/google/android/material/card/MaterialCardView;
|
||||
public final field planView Lme/proton/core/plan/presentation/view/DynamicPlanView;
|
||||
public static fun bind (Landroid/view/View;)Lme/proton/core/plan/presentation/databinding/DynamicPlanCardviewBinding;
|
||||
public fun getRoot ()Landroid/view/View;
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;)Lme/proton/core/plan/presentation/databinding/DynamicPlanCardviewBinding;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/databinding/DynamicPlanViewBinding : androidx/viewbinding/ViewBinding {
|
||||
@@ -165,9 +177,25 @@ public final class me/proton/core/plan/presentation/databinding/DynamicPlanViewB
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;)Lme/proton/core/plan/presentation/databinding/DynamicPlanViewBinding;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/databinding/FragmentDynamicPlanListBinding : androidx/viewbinding/ViewBinding {
|
||||
public final field error Landroid/widget/TextView;
|
||||
public final field errorLayout Landroid/widget/LinearLayout;
|
||||
public final field plans Landroid/widget/LinearLayout;
|
||||
public final field progress Landroid/widget/ProgressBar;
|
||||
public final field retry Landroid/widget/Button;
|
||||
public static fun bind (Landroid/view/View;)Lme/proton/core/plan/presentation/databinding/FragmentDynamicPlanListBinding;
|
||||
public synthetic fun getRoot ()Landroid/view/View;
|
||||
public fun getRoot ()Landroid/widget/FrameLayout;
|
||||
public static fun inflate (Landroid/view/LayoutInflater;)Lme/proton/core/plan/presentation/databinding/FragmentDynamicPlanListBinding;
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Z)Lme/proton/core/plan/presentation/databinding/FragmentDynamicPlanListBinding;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/databinding/FragmentDynamicSubscriptionBinding : androidx/viewbinding/ViewBinding {
|
||||
public final field dynamicPlan Lme/proton/core/plan/presentation/view/DynamicPlanView;
|
||||
public final field error Landroid/widget/TextView;
|
||||
public final field errorLayout Landroid/widget/LinearLayout;
|
||||
public final field progress Landroid/widget/ProgressBar;
|
||||
public final field retry Landroid/widget/Button;
|
||||
public static fun bind (Landroid/view/View;)Lme/proton/core/plan/presentation/databinding/FragmentDynamicSubscriptionBinding;
|
||||
public synthetic fun getRoot ()Landroid/view/View;
|
||||
public fun getRoot ()Landroidx/constraintlayout/widget/ConstraintLayout;
|
||||
@@ -277,6 +305,23 @@ public final class me/proton/core/plan/presentation/databinding/PlansListViewBin
|
||||
public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Z)Lme/proton/core/plan/presentation/databinding/PlansListViewBinding;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/entity/DynamicPlanFilter {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Lme/proton/core/domain/entity/UserId;ILjava/lang/String;)V
|
||||
public synthetic fun <init> (Lme/proton/core/domain/entity/UserId;ILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Lme/proton/core/domain/entity/UserId;
|
||||
public final fun component2 ()I
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun copy (Lme/proton/core/domain/entity/UserId;ILjava/lang/String;)Lme/proton/core/plan/presentation/entity/DynamicPlanFilter;
|
||||
public static synthetic fun copy$default (Lme/proton/core/plan/presentation/entity/DynamicPlanFilter;Lme/proton/core/domain/entity/UserId;ILjava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/presentation/entity/DynamicPlanFilter;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getCurrency ()Ljava/lang/String;
|
||||
public final fun getCycle ()I
|
||||
public final fun getUserId ()Lme/proton/core/domain/entity/UserId;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/entity/PlanCurrency : java/lang/Enum {
|
||||
public static final field CHF Lme/proton/core/plan/presentation/entity/PlanCurrency;
|
||||
public static final field Companion Lme/proton/core/plan/presentation/entity/PlanCurrency$Companion;
|
||||
@@ -290,6 +335,7 @@ public final class me/proton/core/plan/presentation/entity/PlanCurrency : java/l
|
||||
|
||||
public final class me/proton/core/plan/presentation/entity/PlanCurrency$Companion {
|
||||
public final fun getMap ()Ljava/util/Map;
|
||||
public final fun getMapName ()Ljava/util/Map;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/entity/PlanCycle : java/lang/Enum {
|
||||
@@ -814,9 +860,22 @@ public abstract class me/proton/core/plan/presentation/ui/BasePlansFragment : me
|
||||
public final class me/proton/core/plan/presentation/ui/BasePlansFragment$Companion {
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/ui/DynamicPlanListFragment : me/proton/core/presentation/ui/ProtonFragment {
|
||||
public fun <init> ()V
|
||||
public fun onViewCreated (Landroid/view/View;Landroid/os/Bundle;)V
|
||||
public final fun setCurrency (Ljava/lang/String;)V
|
||||
public final fun setOnPlanSelected (Lkotlin/jvm/functions/Function1;)V
|
||||
public final fun setUserId (Lme/proton/core/domain/entity/UserId;)V
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/plan/presentation/ui/DynamicPlanListFragment_GeneratedInjector {
|
||||
public abstract fun injectDynamicPlanListFragment (Lme/proton/core/plan/presentation/ui/DynamicPlanListFragment;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/ui/DynamicSubscriptionFragment : me/proton/core/presentation/ui/ProtonFragment {
|
||||
public fun <init> ()V
|
||||
public fun onViewCreated (Landroid/view/View;Landroid/os/Bundle;)V
|
||||
public final fun setUserId (Lme/proton/core/domain/entity/UserId;)V
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/plan/presentation/ui/DynamicSubscriptionFragment_GeneratedInjector {
|
||||
@@ -833,6 +892,19 @@ public final class me/proton/core/plan/presentation/ui/FragmentOrchestratorKt {
|
||||
public static synthetic fun showPlansSignup$default (Landroidx/fragment/app/FragmentManager;ILme/proton/core/plan/presentation/entity/PlanInput;ILjava/lang/Object;)Landroidx/fragment/app/Fragment;
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/presentation/ui/Hilt_DynamicPlanListFragment : me/proton/core/presentation/ui/ProtonFragment, dagger/hilt/internal/GeneratedComponentManagerHolder {
|
||||
public final fun componentManager ()Ldagger/hilt/android/internal/managers/FragmentComponentManager;
|
||||
public synthetic fun componentManager ()Ldagger/hilt/internal/GeneratedComponentManager;
|
||||
protected fun createComponentManager ()Ldagger/hilt/android/internal/managers/FragmentComponentManager;
|
||||
public final fun generatedComponent ()Ljava/lang/Object;
|
||||
public fun getContext ()Landroid/content/Context;
|
||||
public fun getDefaultViewModelProviderFactory ()Landroidx/lifecycle/ViewModelProvider$Factory;
|
||||
protected fun inject ()V
|
||||
public fun onAttach (Landroid/app/Activity;)V
|
||||
public fun onAttach (Landroid/content/Context;)V
|
||||
public fun onGetLayoutInflater (Landroid/os/Bundle;)Landroid/view/LayoutInflater;
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/presentation/ui/Hilt_DynamicSubscriptionFragment : me/proton/core/presentation/ui/ProtonFragment, dagger/hilt/internal/GeneratedComponentManagerHolder {
|
||||
public final fun componentManager ()Ldagger/hilt/android/internal/managers/FragmentComponentManager;
|
||||
public synthetic fun componentManager ()Ldagger/hilt/internal/GeneratedComponentManager;
|
||||
@@ -1006,7 +1078,7 @@ public final class me/proton/core/plan/presentation/usecase/RedeemGooglePurchase
|
||||
public static fun newInstance (Ljava/util/Optional;Lme/proton/core/payment/domain/usecase/CreatePaymentToken;Lme/proton/core/payment/domain/usecase/PerformSubscribe;Lme/proton/core/payment/domain/usecase/ValidateSubscriptionPlan;)Lme/proton/core/plan/presentation/usecase/RedeemGooglePurchase;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/view/DynamicPlanEntitlementDescriptionView : androidx/constraintlayout/widget/ConstraintLayout {
|
||||
public final class me/proton/core/plan/presentation/view/DynamicEntitlementDescriptionView : androidx/constraintlayout/widget/ConstraintLayout {
|
||||
public fun <init> (Landroid/content/Context;)V
|
||||
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
@@ -1018,11 +1090,11 @@ public final class me/proton/core/plan/presentation/view/DynamicPlanEntitlementD
|
||||
public final fun setText (Ljava/lang/CharSequence;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/view/DynamicPlanEntitlementKt {
|
||||
public static final fun toView (Lme/proton/core/plan/domain/entity/DynamicPlanEntitlement;Landroid/content/Context;)Landroidx/constraintlayout/widget/ConstraintLayout;
|
||||
public final class me/proton/core/plan/presentation/view/DynamicEntitlementKt {
|
||||
public static final fun toView (Lme/proton/core/plan/domain/entity/DynamicEntitlement;Landroid/content/Context;)Landroidx/constraintlayout/widget/ConstraintLayout;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/view/DynamicPlanEntitlementStorageView : androidx/constraintlayout/widget/ConstraintLayout {
|
||||
public final class me/proton/core/plan/presentation/view/DynamicEntitlementStorageView : androidx/constraintlayout/widget/ConstraintLayout {
|
||||
public fun <init> (Landroid/content/Context;)V
|
||||
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
@@ -1034,6 +1106,17 @@ public final class me/proton/core/plan/presentation/view/DynamicPlanEntitlementS
|
||||
public final fun setText (Ljava/lang/CharSequence;)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/view/DynamicPlanCardView : androidx/constraintlayout/widget/ConstraintLayout {
|
||||
public fun <init> (Landroid/content/Context;)V
|
||||
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getCardView ()Lcom/google/android/material/card/MaterialCardView;
|
||||
public final fun getPlanView ()Lme/proton/core/plan/presentation/view/DynamicPlanView;
|
||||
public final fun isCollapsed ()Z
|
||||
public final fun setCollapsed (Z)V
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/view/DynamicPlanView : androidx/constraintlayout/widget/ConstraintLayout {
|
||||
public fun <init> (Landroid/content/Context;)V
|
||||
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
@@ -1049,7 +1132,6 @@ public final class me/proton/core/plan/presentation/view/DynamicPlanView : andro
|
||||
public final fun getPromoPercentage ()Ljava/lang/CharSequence;
|
||||
public final fun getPromoTitle ()Ljava/lang/CharSequence;
|
||||
public final fun getRenewalText ()Ljava/lang/CharSequence;
|
||||
public final fun getRenewalTextIsVisible ()Z
|
||||
public final fun getStarred ()Z
|
||||
public final fun getTitle ()Ljava/lang/CharSequence;
|
||||
public final fun isCollapsable ()Z
|
||||
@@ -1059,13 +1141,13 @@ public final class me/proton/core/plan/presentation/view/DynamicPlanView : andro
|
||||
public final fun setCollapsable (Z)V
|
||||
public final fun setCollapsed (Z)V
|
||||
public final fun setDescription (Ljava/lang/CharSequence;)V
|
||||
public final fun setOnButtonClickListener (Landroid/view/View$OnClickListener;)V
|
||||
public final fun setPriceCycle (Ljava/lang/CharSequence;)V
|
||||
public final fun setPricePercentage (Ljava/lang/CharSequence;)V
|
||||
public final fun setPriceText (Ljava/lang/CharSequence;)V
|
||||
public final fun setPromoPercentage (Ljava/lang/CharSequence;)V
|
||||
public final fun setPromoTitle (Ljava/lang/CharSequence;)V
|
||||
public final fun setRenewalText (Ljava/lang/CharSequence;)V
|
||||
public final fun setRenewalTextIsVisible (Z)V
|
||||
public final fun setStarred (Z)V
|
||||
public final fun setTitle (Ljava/lang/CharSequence;)V
|
||||
}
|
||||
@@ -1091,28 +1173,28 @@ public final class me/proton/core/plan/presentation/view/PlanViewUtilsKt {
|
||||
public static final field HUNDRED_PERCENT I
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel_Factory : dagger/internal/Factory {
|
||||
public fun <init> (Ljavax/inject/Provider;)V
|
||||
public static fun create (Ljavax/inject/Provider;)Lme/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel_Factory;
|
||||
public final class me/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel_Factory : dagger/internal/Factory {
|
||||
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
|
||||
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel_Factory;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Lme/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel;
|
||||
public static fun newInstance (Lme/proton/core/plan/domain/usecase/GetDynamicPlans;)Lme/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel;
|
||||
public fun get ()Lme/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel;
|
||||
public static fun newInstance (Lme/proton/core/observability/domain/ObservabilityManager;Lme/proton/core/user/domain/UserManager;Lme/proton/core/accountmanager/domain/AccountManager;Lme/proton/core/plan/domain/usecase/GetDynamicPlans;)Lme/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel_HiltModules {
|
||||
public final class me/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel_HiltModules {
|
||||
}
|
||||
|
||||
public abstract class me/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel_HiltModules$BindsModule {
|
||||
public abstract fun binds (Lme/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel;)Landroidx/lifecycle/ViewModel;
|
||||
public abstract class me/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel_HiltModules$BindsModule {
|
||||
public abstract fun binds (Lme/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel;)Landroidx/lifecycle/ViewModel;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel_HiltModules$KeyModule {
|
||||
public final class me/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel_HiltModules$KeyModule {
|
||||
public static fun provide ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class me/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel_HiltModules_KeyModule_ProvideFactory : dagger/internal/Factory {
|
||||
public final class me/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel_HiltModules_KeyModule_ProvideFactory : dagger/internal/Factory {
|
||||
public fun <init> ()V
|
||||
public static fun create ()Lme/proton/core/plan/presentation/viewmodel/DynamicPlansViewModel_HiltModules_KeyModule_ProvideFactory;
|
||||
public static fun create ()Lme/proton/core/plan/presentation/viewmodel/DynamicPlanListViewModel_HiltModules_KeyModule_ProvideFactory;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Ljava/lang/String;
|
||||
public static fun provide ()Ljava/lang/String;
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.presentation.entity
|
||||
|
||||
import me.proton.core.domain.entity.UserId
|
||||
|
||||
data class DynamicPlanFilter(
|
||||
val userId: UserId? = null,
|
||||
val cycle: Int = 12,
|
||||
val currency: String? = null,
|
||||
)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.presentation.entity
|
||||
|
||||
import me.proton.core.plan.domain.entity.DynamicPlan
|
||||
import me.proton.core.plan.domain.entity.isFree
|
||||
import me.proton.core.plan.presentation.viewmodel.toPlanVendorDetailsMap
|
||||
|
||||
internal fun DynamicPlan.getSelectedPlan(
|
||||
cycle: Int,
|
||||
currency: String?
|
||||
): SelectedPlan = SelectedPlan(
|
||||
planName = name,
|
||||
planDisplayName = title,
|
||||
free = isFree(),
|
||||
cycle = PlanCycle.map[cycle] ?: PlanCycle.FREE,
|
||||
currency = PlanCurrency.mapName[currency] ?: PlanCurrency.CHF,
|
||||
amount = instances[cycle]?.price?.get(currency)?.current?.toDouble() ?: 0.0,
|
||||
services = services.sumOf { it.code },
|
||||
type = type?.value ?: 0,
|
||||
vendorNames = instances[cycle]?.vendors?.toPlanVendorDetailsMap().orEmpty()
|
||||
)
|
||||
+1
@@ -34,5 +34,6 @@ enum class PlanCurrency(val sign: String) {
|
||||
|
||||
companion object {
|
||||
val map = values().associateBy { it.sign }
|
||||
val mapName = values().associateBy { it.name }
|
||||
}
|
||||
}
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.presentation.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.plan.domain.entity.DynamicDecoration
|
||||
import me.proton.core.plan.domain.entity.DynamicPlan
|
||||
import me.proton.core.plan.presentation.R
|
||||
import me.proton.core.plan.presentation.databinding.FragmentDynamicPlanListBinding
|
||||
import me.proton.core.plan.presentation.entity.SelectedPlan
|
||||
import me.proton.core.plan.presentation.entity.getSelectedPlan
|
||||
import me.proton.core.plan.presentation.view.DynamicPlanCardView
|
||||
import me.proton.core.plan.presentation.view.DynamicPlanView
|
||||
import me.proton.core.plan.presentation.view.toView
|
||||
import me.proton.core.plan.presentation.viewmodel.DynamicPlanListViewModel
|
||||
import me.proton.core.plan.presentation.viewmodel.DynamicPlanListViewModel.Action
|
||||
import me.proton.core.plan.presentation.viewmodel.DynamicPlanListViewModel.State
|
||||
import me.proton.core.presentation.ui.ProtonFragment
|
||||
import me.proton.core.presentation.utils.formatCentsPriceDefaultLocale
|
||||
import me.proton.core.presentation.utils.getUserMessage
|
||||
import me.proton.core.presentation.utils.onClick
|
||||
import me.proton.core.presentation.utils.viewBinding
|
||||
import kotlin.math.abs
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@AndroidEntryPoint
|
||||
class DynamicPlanListFragment : ProtonFragment(R.layout.fragment_dynamic_plan_list) {
|
||||
|
||||
private val binding by viewBinding(FragmentDynamicPlanListBinding::bind)
|
||||
private val viewModel by viewModels<DynamicPlanListViewModel>()
|
||||
|
||||
private var onPlanSelected: ((SelectedPlan) -> Unit)? = null
|
||||
|
||||
fun setOnPlanSelected(onPlanSelected: (SelectedPlan) -> Unit) {
|
||||
this.onPlanSelected = onPlanSelected
|
||||
}
|
||||
|
||||
fun setUserId(userId: UserId) {
|
||||
viewModel.perform(Action.SetUserId(userId))
|
||||
}
|
||||
|
||||
fun setCurrency(currency: String) {
|
||||
viewModel.perform(Action.SetCurrency(currency))
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel.state.onEach {
|
||||
when (it) {
|
||||
is State.Loading -> onLoading()
|
||||
is State.Error -> onError(it.error)
|
||||
is State.Success -> onSuccess(it)
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
binding.retry.onClick { viewModel.perform(Action.Load) }
|
||||
}
|
||||
|
||||
private fun onLoading() {
|
||||
showLoading(true)
|
||||
}
|
||||
|
||||
private fun onError(error: Throwable?) = with(binding) {
|
||||
showLoading(false)
|
||||
val message = error?.getUserMessage(resources)
|
||||
showError(message ?: getString(R.string.presentation_error_general))
|
||||
}
|
||||
|
||||
private fun onSuccess(result: State.Success) {
|
||||
showLoading(false)
|
||||
showPlans(result.plans, result.filter.cycle, result.filter.currency)
|
||||
}
|
||||
|
||||
private fun showLoading(loading: Boolean) = with(binding) {
|
||||
progress.visibility = if (loading) View.VISIBLE else View.GONE
|
||||
errorLayout.visibility = View.GONE
|
||||
binding.plans.removeAllViews()
|
||||
}
|
||||
|
||||
private fun showError(message: String) = with(binding) {
|
||||
errorLayout.visibility = View.VISIBLE
|
||||
error.text = message
|
||||
}
|
||||
|
||||
private fun showPlans(plans: List<DynamicPlan>, cycle: Int, currency: String?) {
|
||||
binding.plans.removeAllViews()
|
||||
plans.forEach { plan ->
|
||||
val cardView = DynamicPlanCardView(requireContext())
|
||||
val selectedPlan = plan.getSelectedPlan(cycle, currency)
|
||||
cardView.planView.setPlan(plan, cycle, currency)
|
||||
cardView.planView.setOnButtonClickListener { onPlanSelected?.invoke(selectedPlan) }
|
||||
binding.plans.addView(cardView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DynamicPlanView.setPlan(plan: DynamicPlan, cycle: Int, currency: String?) {
|
||||
val instance = plan.instances[cycle]
|
||||
val price = instance?.price?.get(currency)
|
||||
id = abs(plan.name.hashCode())
|
||||
title = plan.title
|
||||
description = plan.description
|
||||
starred = plan.decorations.filterIsInstance<DynamicDecoration.Star>().isNotEmpty()
|
||||
priceText = price?.current?.toDouble()?.formatCentsPriceDefaultLocale(price.currency)
|
||||
priceCycle = instance?.description
|
||||
isCollapsable = true
|
||||
entitlements.removeAllViews()
|
||||
plan.entitlements.forEach { entitlements.addView(it.toView(context)) }
|
||||
buttonTextIsVisible = true
|
||||
buttonText = String.format(context.getString(R.string.plans_get_proton), plan.title)
|
||||
}
|
||||
}
|
||||
+35
-17
@@ -27,17 +27,20 @@ import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.payment.domain.entity.DynamicSubscription
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanDecoration
|
||||
import me.proton.core.plan.domain.entity.DynamicDecoration
|
||||
import me.proton.core.plan.presentation.R
|
||||
import me.proton.core.plan.presentation.databinding.FragmentDynamicSubscriptionBinding
|
||||
import me.proton.core.plan.presentation.view.formatRenew
|
||||
import me.proton.core.plan.presentation.view.toView
|
||||
import me.proton.core.plan.presentation.viewmodel.DynamicSubscriptionViewModel
|
||||
import me.proton.core.plan.presentation.viewmodel.DynamicSubscriptionViewModel.Action
|
||||
import me.proton.core.plan.presentation.viewmodel.DynamicSubscriptionViewModel.State
|
||||
import me.proton.core.presentation.ui.ProtonFragment
|
||||
import me.proton.core.presentation.utils.errorSnack
|
||||
import me.proton.core.presentation.utils.formatCentsPriceDefaultLocale
|
||||
import me.proton.core.presentation.utils.getUserMessage
|
||||
import me.proton.core.presentation.utils.onClick
|
||||
import me.proton.core.presentation.utils.viewBinding
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -46,16 +49,22 @@ class DynamicSubscriptionFragment : ProtonFragment(R.layout.fragment_dynamic_sub
|
||||
private val binding by viewBinding(FragmentDynamicSubscriptionBinding::bind)
|
||||
private val viewModel by viewModels<DynamicSubscriptionViewModel>()
|
||||
|
||||
fun setUserId(userId: UserId?) {
|
||||
viewModel.perform(Action.SetUserId(userId))
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewModel.state.onEach {
|
||||
when (it) {
|
||||
is DynamicSubscriptionViewModel.State.Loading -> onLoading()
|
||||
is DynamicSubscriptionViewModel.State.Error -> onError(it.error)
|
||||
is DynamicSubscriptionViewModel.State.UserNotExist -> onNoPrimaryUser()
|
||||
is DynamicSubscriptionViewModel.State.Success -> onSuccess(it.dynamicSubscription)
|
||||
is State.Loading -> onLoading()
|
||||
is State.Error -> onError(it.error)
|
||||
is State.UserNotExist -> onNoPrimaryUser()
|
||||
is State.Success -> onSuccess(it.dynamicSubscription)
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
binding.retry.onClick { viewModel.perform(Action.Load) }
|
||||
}
|
||||
|
||||
private fun onLoading() {
|
||||
@@ -65,11 +74,12 @@ class DynamicSubscriptionFragment : ProtonFragment(R.layout.fragment_dynamic_sub
|
||||
private fun onError(error: Throwable?) = with(binding) {
|
||||
showLoading(false)
|
||||
val message = error?.getUserMessage(resources)
|
||||
root.errorSnack(message ?: getString(R.string.presentation_error_general))
|
||||
showError(message ?: getString(R.string.presentation_error_general))
|
||||
}
|
||||
|
||||
private fun onNoPrimaryUser() {
|
||||
showLoading(true)
|
||||
showError(getString(R.string.presentation_error_general))
|
||||
}
|
||||
|
||||
private fun onSuccess(dynamicSubscription: DynamicSubscription) {
|
||||
@@ -79,20 +89,28 @@ class DynamicSubscriptionFragment : ProtonFragment(R.layout.fragment_dynamic_sub
|
||||
|
||||
private fun showLoading(loading: Boolean) = with(binding) {
|
||||
progress.visibility = if (loading) VISIBLE else GONE
|
||||
errorLayout.visibility = GONE
|
||||
}
|
||||
|
||||
private fun showSubscription(dynamicSubscription: DynamicSubscription) = with(binding.dynamicPlan) {
|
||||
title = dynamicSubscription.title
|
||||
description = dynamicSubscription.description
|
||||
starred = dynamicSubscription.decorations.filterIsInstance<DynamicPlanDecoration.Star>().isNotEmpty()
|
||||
val price = dynamicSubscription.renewAmount?.takeIf { it > 0 } ?: dynamicSubscription.amount
|
||||
priceText = price.toDouble().formatCentsPriceDefaultLocale(dynamicSubscription.currency)
|
||||
priceCycle = dynamicSubscription.cycleDescription
|
||||
renewalTextIsVisible = dynamicSubscription.renewAmount != null && dynamicSubscription.renew
|
||||
renewalText = formatRenew(context, dynamicSubscription.renew, dynamicSubscription.periodEnd)
|
||||
private fun showError(message: String) = with(binding) {
|
||||
errorLayout.visibility = VISIBLE
|
||||
error.text = message
|
||||
}
|
||||
|
||||
private fun showSubscription(subscription: DynamicSubscription) = with(binding.dynamicPlan) {
|
||||
title = subscription.title
|
||||
description = subscription.description
|
||||
starred = subscription.decorations.filterIsInstance<DynamicDecoration.Star>().isNotEmpty()
|
||||
val price = subscription.renewAmount?.takeIf { it > 0 } ?: subscription.amount
|
||||
priceText = price?.toDouble()?.formatCentsPriceDefaultLocale(requireNotNull(subscription.currency))
|
||||
priceCycle = subscription.cycleDescription
|
||||
renewalText = when {
|
||||
subscription.renew == null -> null
|
||||
subscription.periodEnd == null -> null
|
||||
else -> formatRenew(context, subscription.renew ?: false, requireNotNull(subscription.periodEnd))
|
||||
}
|
||||
isCollapsable = false
|
||||
entitlements.removeAllViews()
|
||||
dynamicSubscription.entitlements.forEach { entitlements.addView(it.toView(context)) }
|
||||
subscription.entitlements.forEach { entitlements.addView(it.toView(context)) }
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -22,19 +22,19 @@ package me.proton.core.plan.presentation.view
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import me.proton.core.plan.domain.entity.DynamicPlanEntitlement
|
||||
import me.proton.core.plan.domain.entity.DynamicEntitlement
|
||||
import me.proton.core.plan.presentation.R
|
||||
import me.proton.core.util.kotlin.takeIfNotBlank
|
||||
|
||||
fun DynamicPlanEntitlement.toView(context: Context) = when (this) {
|
||||
is DynamicPlanEntitlement.Description -> DynamicPlanEntitlementDescriptionView(context).apply {
|
||||
icon = this@toView.iconBase64.takeIfNotBlank()?.toByteArray() ?: getFallbackIcon(context)
|
||||
fun DynamicEntitlement.toView(context: Context) = when (this) {
|
||||
is DynamicEntitlement.Description -> DynamicEntitlementDescriptionView(context).apply {
|
||||
icon = this@toView.iconUrl.takeIfNotBlank() ?: getFallbackIcon(context)
|
||||
text = this@toView.text
|
||||
}
|
||||
|
||||
is DynamicPlanEntitlement.Storage -> DynamicPlanEntitlementStorageView(context).apply {
|
||||
text = formatUsedSpace(context, currentMBytes / 100, maxMBytes / 100)
|
||||
progress = ((currentMBytes.toFloat() / maxMBytes.toFloat()) * 100).toInt()
|
||||
is DynamicEntitlement.Storage -> DynamicEntitlementStorageView(context).apply {
|
||||
text = formatUsedSpace(context, currentBytes, maxBytes)
|
||||
progress = ((currentBytes.toFloat() / maxBytes.toFloat()) * 100).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -12,12 +12,13 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import coil.ImageLoader
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.load
|
||||
import me.proton.core.plan.presentation.databinding.DynamicPlanEntitlementDescriptionViewBinding.inflate
|
||||
import me.proton.core.plan.presentation.R
|
||||
import me.proton.core.plan.presentation.databinding.DynamicEntitlementDescriptionViewBinding.inflate
|
||||
import okhttp3.HttpUrl
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class DynamicPlanEntitlementDescriptionView @JvmOverloads constructor(
|
||||
class DynamicEntitlementDescriptionView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
@@ -45,7 +46,7 @@ class DynamicPlanEntitlementDescriptionView @JvmOverloads constructor(
|
||||
*/
|
||||
var icon: Any? = null
|
||||
set(value) = with(binding) {
|
||||
icon.load(value, imageLoader)
|
||||
icon.load(value, imageLoader) { fallback(R.drawable.ic_proton_checkmark) }
|
||||
}
|
||||
|
||||
var text: CharSequence?
|
||||
+3
-2
@@ -7,9 +7,10 @@ import androidx.annotation.StyleRes
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import me.proton.core.plan.presentation.R
|
||||
import me.proton.core.plan.presentation.databinding.DynamicPlanEntitlementStorageViewBinding.inflate
|
||||
import me.proton.core.plan.presentation.databinding.DynamicEntitlementStorageViewBinding.inflate
|
||||
|
||||
class DynamicPlanEntitlementStorageView @JvmOverloads constructor(
|
||||
@Suppress("MagicNumber")
|
||||
class DynamicEntitlementStorageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.presentation.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import me.proton.core.plan.presentation.R
|
||||
import me.proton.core.plan.presentation.databinding.DynamicPlanCardviewBinding.inflate
|
||||
import me.proton.core.presentation.utils.onClick
|
||||
|
||||
class DynamicPlanCardView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val binding by lazy { inflate(LayoutInflater.from(context), this) }
|
||||
|
||||
val cardView: MaterialCardView = binding.cardView
|
||||
val planView: DynamicPlanView = binding.planView
|
||||
|
||||
init {
|
||||
context.withStyledAttributes(attrs, R.styleable.DynamicPlanCardView) {
|
||||
isCollapsed = getBoolean(R.styleable.DynamicPlanCardView_isCollapsed, false)
|
||||
}
|
||||
planView.isFocusable = false // Prevent child to take focus.
|
||||
planView.isClickable = false // Prevent child to take clicks.
|
||||
cardView.onClick {
|
||||
isCollapsed = !isCollapsed
|
||||
}
|
||||
}
|
||||
|
||||
var isCollapsed: Boolean
|
||||
get() = planView.isCollapsed
|
||||
set(value) {
|
||||
planView.isCollapsed = value
|
||||
cardView.isChecked = !isCollapsed
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -88,12 +88,14 @@ class DynamicPlanView @JvmOverloads constructor(
|
||||
get() = binding.priceCycle.text
|
||||
set(value) {
|
||||
binding.priceCycle.text = value
|
||||
binding.priceCycle.isVisible = !priceCycle.isNullOrBlank()
|
||||
}
|
||||
|
||||
var pricePercentage: CharSequence?
|
||||
get() = binding.pricePercentage.text
|
||||
set(value) {
|
||||
binding.pricePercentage.text = value
|
||||
binding.pricePercentage.isVisible = !pricePercentage.isNullOrBlank()
|
||||
binding.priceLayout.isVisible = !priceText.isNullOrBlank()
|
||||
}
|
||||
|
||||
@@ -121,17 +123,13 @@ class DynamicPlanView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
var renewalTextIsVisible: Boolean
|
||||
get() = binding.contentRenewal.isVisible
|
||||
set(value) {
|
||||
binding.contentRenewal.isVisible = value
|
||||
binding.contentSeparator.isVisible = value
|
||||
}
|
||||
|
||||
var renewalText: CharSequence?
|
||||
get() = binding.contentRenewal.text
|
||||
set(value) {
|
||||
binding.contentRenewal.text = value
|
||||
binding.contentRenewal.isVisible = !renewalText.isNullOrBlank()
|
||||
binding.contentSeparator.isVisible = !renewalText.isNullOrBlank()
|
||||
}
|
||||
|
||||
var buttonTextIsVisible: Boolean
|
||||
@@ -145,6 +143,10 @@ class DynamicPlanView @JvmOverloads constructor(
|
||||
set(value) {
|
||||
binding.contentButton.text = value
|
||||
}
|
||||
|
||||
fun setOnButtonClickListener(listener: OnClickListener) {
|
||||
binding.contentButton.setOnClickListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.rotate(degrees: Float) {
|
||||
|
||||
+3
-3
@@ -87,10 +87,10 @@ internal fun User?.calculateUsedSpacePercentage(): Double {
|
||||
return usedSpace.toDouble() / maxSpace.toDouble() * HUNDRED_PERCENT
|
||||
}
|
||||
|
||||
internal fun formatUsedSpace(context: Context, usedSpace: Long, maxSpace: Long): String = String.format(
|
||||
internal fun formatUsedSpace(context: Context, usedBytes: Long, maxBytes: Long): String = String.format(
|
||||
context.getString(R.string.plans_used_space),
|
||||
usedSpace.formatByteToHumanReadable(),
|
||||
maxSpace.formatByteToHumanReadable()
|
||||
usedBytes.formatByteToHumanReadable(),
|
||||
maxBytes.formatByteToHumanReadable()
|
||||
)
|
||||
|
||||
internal fun formatRenew(context: Context, renew: Boolean, periodEnd: Instant): Spanned {
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.presentation.viewmodel
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.observability.domain.ObservabilityContext
|
||||
import me.proton.core.observability.domain.ObservabilityManager
|
||||
import me.proton.core.plan.domain.entity.DynamicPlan
|
||||
import me.proton.core.plan.domain.entity.filterBy
|
||||
import me.proton.core.plan.domain.usecase.GetDynamicPlans
|
||||
import me.proton.core.plan.presentation.entity.DynamicPlanFilter
|
||||
import me.proton.core.presentation.viewmodel.ProtonViewModel
|
||||
import me.proton.core.user.domain.UserManager
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class DynamicPlanListViewModel @Inject constructor(
|
||||
override val manager: ObservabilityManager,
|
||||
private val userManager: UserManager,
|
||||
private val accountManager: AccountManager,
|
||||
private val getDynamicPlans: GetDynamicPlans
|
||||
) : ProtonViewModel(), ObservabilityContext {
|
||||
|
||||
sealed class State {
|
||||
object Loading : State()
|
||||
data class Success(val plans: List<DynamicPlan>, val filter: DynamicPlanFilter) : State()
|
||||
data class Error(val error: Throwable) : State()
|
||||
}
|
||||
|
||||
sealed class Action {
|
||||
object Load : Action()
|
||||
data class SetUserId(val userId: UserId) : Action()
|
||||
data class SetCycle(val cycle: Int) : Action()
|
||||
data class SetCurrency(val currency: String) : Action()
|
||||
}
|
||||
|
||||
private val mutableLoadCount = MutableStateFlow(1)
|
||||
private val mutableUserId = MutableStateFlow<UserId?>(null)
|
||||
private val mutablePlanFilter = MutableStateFlow(DynamicPlanFilter())
|
||||
|
||||
private val cycleFilter = mutablePlanFilter.mapLatest { it.cycle }.distinctUntilChanged()
|
||||
private val currencyFilter = mutablePlanFilter.mapLatest { it.currency }.distinctUntilChanged()
|
||||
|
||||
val state: StateFlow<State> = observeUserDynamicPlans().stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = State.Loading
|
||||
)
|
||||
|
||||
private fun observeUserDynamicPlans() = mutableLoadCount
|
||||
.flatMapLatest { observeUserId() }
|
||||
.flatMapLatest { observeFilter(it) }
|
||||
.flatMapLatest { loadDynamicPlans(it) }
|
||||
|
||||
private fun observeUserId(): Flow<UserId?> = mutableUserId.flatMapLatest { userId ->
|
||||
when (userId) {
|
||||
null -> accountManager.getPrimaryUserId()
|
||||
else -> accountManager.getAccount(userId).mapLatest { it?.userId }
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeFilter(userId: UserId?) = combine(
|
||||
cycleFilter,
|
||||
observeCurrency(userId)
|
||||
) { cycle, currency ->
|
||||
DynamicPlanFilter(userId, cycle, currency)
|
||||
}
|
||||
|
||||
private fun observeCurrency(userId: UserId?): Flow<String?> = currencyFilter.flatMapLatest { currency ->
|
||||
when (currency) {
|
||||
null -> userId?.let { userManager.observeUser(it).mapLatest { user -> user?.currency } } ?: flowOf(null)
|
||||
else -> flowOf(currency)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadDynamicPlans(filter: DynamicPlanFilter) = flow {
|
||||
emit(State.Loading)
|
||||
val filteredPlans = getDynamicPlans(filter.userId).filterBy(filter.cycle, filter.currency)
|
||||
emit(State.Success(filteredPlans, filter))
|
||||
}.catch { emit(State.Error(it)) }
|
||||
|
||||
fun perform(action: Action) = when (action) {
|
||||
is Action.Load -> onLoad()
|
||||
is Action.SetUserId -> onSetUserId(action.userId)
|
||||
is Action.SetCycle -> onSetCycle(action.cycle)
|
||||
is Action.SetCurrency -> onSetCurrency(action.currency)
|
||||
}
|
||||
|
||||
private fun onLoad() = viewModelScope.launch {
|
||||
mutableLoadCount.emit(mutableLoadCount.value + 1)
|
||||
}
|
||||
|
||||
private fun onSetUserId(userId: UserId?) = viewModelScope.launch {
|
||||
mutableUserId.emit(userId)
|
||||
}
|
||||
|
||||
private fun onSetCycle(cycle: Int) = viewModelScope.launch {
|
||||
mutablePlanFilter.emit(mutablePlanFilter.value.copy(cycle = cycle))
|
||||
}
|
||||
|
||||
private fun onSetCurrency(currency: String?) = viewModelScope.launch {
|
||||
mutablePlanFilter.emit(mutablePlanFilter.value.copy(currency = currency))
|
||||
}
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.presentation.viewmodel
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.plan.domain.entity.DynamicPlan
|
||||
import me.proton.core.plan.domain.usecase.GetDynamicPlans
|
||||
import me.proton.core.presentation.viewmodel.ProtonViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class DynamicPlansViewModel @Inject constructor(
|
||||
private val getDynamicPlans: GetDynamicPlans
|
||||
) : ProtonViewModel() {
|
||||
sealed class State {
|
||||
object Idle : State()
|
||||
object Loading : State()
|
||||
data class PlansLoaded(val plans: List<DynamicPlan>) : State()
|
||||
data class Error(val throwable: Throwable) : State()
|
||||
}
|
||||
|
||||
|
||||
private val _state: MutableStateFlow<State> = MutableStateFlow(State.Idle)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
/**
|
||||
* Starts loading available plans for the given [userId].
|
||||
*/
|
||||
fun loadPlans(userId: UserId?) = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val plans = getDynamicPlans(userId)
|
||||
emit(State.PlansLoaded(plans))
|
||||
}.catch { error ->
|
||||
_state.tryEmit(State.Error(error))
|
||||
}.onEach {
|
||||
_state.tryEmit(it)
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
+19
-10
@@ -26,9 +26,9 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.domain.entity.UserId
|
||||
@@ -54,9 +54,11 @@ internal class DynamicSubscriptionViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
sealed class Action {
|
||||
object Load : Action()
|
||||
data class SetUserId(val userId: UserId?) : Action()
|
||||
}
|
||||
|
||||
private val mutableLoadCount = MutableStateFlow(1)
|
||||
private val mutableUserId = MutableStateFlow<UserId?>(null)
|
||||
|
||||
val state: StateFlow<State> = observeUserSubscription().stateIn(
|
||||
@@ -65,15 +67,9 @@ internal class DynamicSubscriptionViewModel @Inject constructor(
|
||||
initialValue = State.Loading
|
||||
)
|
||||
|
||||
private fun observeUserSubscription() = observeUserId()
|
||||
.transformLatest { userId ->
|
||||
emit(State.Loading)
|
||||
when (userId) {
|
||||
null -> emit(State.UserNotExist)
|
||||
else -> emit(State.Success(getDynamicSubscription(userId)))
|
||||
}
|
||||
}
|
||||
.catch { emit(State.Error(it)) }
|
||||
private fun observeUserSubscription() = mutableLoadCount
|
||||
.flatMapLatest { observeUserId() }
|
||||
.flatMapLatest { loadDynamicSubscription(it) }
|
||||
|
||||
private fun observeUserId(): Flow<UserId?> = mutableUserId.flatMapLatest { userId ->
|
||||
when (userId) {
|
||||
@@ -82,10 +78,23 @@ internal class DynamicSubscriptionViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadDynamicSubscription(userId: UserId?) = flow {
|
||||
emit(State.Loading)
|
||||
when (userId) {
|
||||
null -> emit(State.UserNotExist)
|
||||
else -> emit(State.Success(getDynamicSubscription(userId)))
|
||||
}
|
||||
}.catch { emit(State.Error(it)) }
|
||||
|
||||
fun perform(action: Action) = when (action) {
|
||||
is Action.Load -> onLoad()
|
||||
is Action.SetUserId -> onSetUserId(action.userId)
|
||||
}
|
||||
|
||||
private fun onLoad() = viewModelScope.launch {
|
||||
mutableLoadCount.emit(mutableLoadCount.value + 1)
|
||||
}
|
||||
|
||||
private fun onSetUserId(userId: UserId?) = viewModelScope.launch {
|
||||
mutableUserId.emit(userId)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/plan_content_item_padding"
|
||||
android:paddingBottom="@dimen/plan_content_item_padding">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/icon_size"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
android:scaleType="centerInside"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?proton_icon_accent"
|
||||
tools:srcCompat="@drawable/ic_proton_checkmark" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
style="@style/Proton.Text.DefaultSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/default_margin"
|
||||
android:layout_marginEnd="@dimen/default_margin"
|
||||
android:gravity="center|start"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="1 of 6 users" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</merge>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/plan_content_item_padding"
|
||||
android:paddingBottom="@dimen/plan_content_item_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
style="@style/Proton.Text.Default"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
app:layout_constraintBottom_toTopOf="@id/progress"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="162 MB of 3 TB" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/gap_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text"
|
||||
app:trackColor="@color/icon_hint"
|
||||
tools:progress="50" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</merge>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardCornerRadius="@dimen/default_corner_radius"
|
||||
app:cardElevation="0dp"
|
||||
app:cardForegroundColor="@color/cardview_checkable_foreground_color"
|
||||
app:cardUseCompatPadding="true"
|
||||
app:checkedIcon="@null"
|
||||
app:strokeColor="@color/cardview_checkable_stroke_color"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanView
|
||||
android:id="@+id/plan_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/corner_padding" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</merge>
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/plan_content_item_padding"
|
||||
android:paddingBottom="@dimen/plan_content_item_padding"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/icon_size"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
android:scaleType="centerInside"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?proton_icon_accent"
|
||||
tools:srcCompat="@drawable/ic_proton_checkmark" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
style="@style/Proton.Text.DefaultSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/default_margin"
|
||||
android:layout_marginEnd="@dimen/default_margin"
|
||||
android:gravity="center|start"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="1 of 6 users" />
|
||||
|
||||
</merge>
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/plan_content_item_padding"
|
||||
android:paddingBottom="@dimen/plan_content_item_padding"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
style="@style/Proton.Text.Default"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="start"
|
||||
app:layout_constraintBottom_toTopOf="@id/progress"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="162 MB of 3 TB" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/gap_medium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text"
|
||||
app:trackColor="@color/icon_hint"
|
||||
tools:progress="50" />
|
||||
|
||||
</merge>
|
||||
@@ -99,7 +99,9 @@
|
||||
android:layout_gravity="end"
|
||||
android:gravity="end"
|
||||
android:maxLines="1"
|
||||
tools:text="per year" />
|
||||
android:visibility="gone"
|
||||
tools:text="per year"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/price_percentage"
|
||||
@@ -110,6 +112,7 @@
|
||||
android:gravity="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/brand_norm"
|
||||
android:visibility="gone"
|
||||
tools:text="(-33%)"
|
||||
tools:visibility="visible" />
|
||||
</LinearLayout>
|
||||
@@ -173,27 +176,28 @@
|
||||
android:id="@+id/content_entitlements"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/gap_large"
|
||||
android:orientation="vertical">
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanEntitlementStorageView
|
||||
<me.proton.core.plan.presentation.view.DynamicEntitlementStorageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanEntitlementDescriptionView
|
||||
<me.proton.core.plan.presentation.view.DynamicEntitlementDescriptionView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanEntitlementDescriptionView
|
||||
<me.proton.core.plan.presentation.view.DynamicEntitlementDescriptionView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanEntitlementDescriptionView
|
||||
<me.proton.core.plan.presentation.view.DynamicEntitlementDescriptionView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/plans"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/corner_padding"
|
||||
tools:visibility="visible">
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:isCollapsed="true"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:isCollapsed="false"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/errorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/corner_padding"
|
||||
android:visibility="gone"
|
||||
tools:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
tools:text="Error message" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/retry"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/gap_medium"
|
||||
android:text="@string/presentation_retry" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -15,19 +15,51 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanView
|
||||
android:id="@+id/dynamic_plan"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/corner_padding" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<me.proton.core.plan.presentation.view.DynamicPlanView
|
||||
android:id="@+id/dynamic_plan"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/corner_padding"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/errorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/corner_padding"
|
||||
android:visibility="gone"
|
||||
tools:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
tools:text="Error message" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/retry"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/gap_medium"
|
||||
android:text="@string/presentation_retry" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="DynamicPlanCardView">
|
||||
<attr name="isCollapsed" format="boolean" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
+6
-7
@@ -20,8 +20,8 @@ package me.proton.core.plan.presentation
|
||||
|
||||
import app.cash.paparazzi.DeviceConfig
|
||||
import app.cash.paparazzi.Paparazzi
|
||||
import me.proton.core.plan.presentation.view.DynamicPlanEntitlementDescriptionView
|
||||
import me.proton.core.plan.presentation.view.DynamicPlanEntitlementStorageView
|
||||
import me.proton.core.plan.presentation.view.DynamicEntitlementDescriptionView
|
||||
import me.proton.core.plan.presentation.view.DynamicEntitlementStorageView
|
||||
import me.proton.core.plan.presentation.view.DynamicPlanView
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -37,7 +37,7 @@ class SnapshotDynamicSubscriptionTest {
|
||||
|
||||
@Test
|
||||
fun dynamicPlanEntitlementDescriptionView() {
|
||||
val view = DynamicPlanEntitlementDescriptionView(paparazzi.context)
|
||||
val view = DynamicEntitlementDescriptionView(paparazzi.context)
|
||||
view.text = "1 of 1 user"
|
||||
view.icon = R.drawable.ic_proton_checkmark
|
||||
paparazzi.snapshot(view)
|
||||
@@ -45,7 +45,7 @@ class SnapshotDynamicSubscriptionTest {
|
||||
|
||||
@Test
|
||||
fun dynamicPlanEntitlementStorageView() {
|
||||
val view = DynamicPlanEntitlementStorageView(paparazzi.context)
|
||||
val view = DynamicEntitlementStorageView(paparazzi.context)
|
||||
view.text = "50/100"
|
||||
view.progress = 50
|
||||
paparazzi.snapshot(view)
|
||||
@@ -61,15 +61,14 @@ class SnapshotDynamicSubscriptionTest {
|
||||
view.pricePercentage = "-50%"
|
||||
view.promoPercentage = "-50%"
|
||||
view.promoTitle = "1 month super promo"
|
||||
view.renewalTextIsVisible = true
|
||||
view.renewalText = "Your plan will automatically renew on 4 Jun 1982."
|
||||
view.isCollapsable = false
|
||||
view.starred = true
|
||||
view.entitlements.addView(DynamicPlanEntitlementStorageView(paparazzi.context).apply {
|
||||
view.entitlements.addView(DynamicEntitlementStorageView(paparazzi.context).apply {
|
||||
text = "50 MB on 100 MB"
|
||||
progress = 50
|
||||
})
|
||||
view.entitlements.addView(DynamicPlanEntitlementDescriptionView(paparazzi.context).apply {
|
||||
view.entitlements.addView(DynamicEntitlementDescriptionView(paparazzi.context).apply {
|
||||
text = "100MB of free Storage"
|
||||
icon = R.drawable.ic_proton_storage
|
||||
})
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.presentation.viewmodel
|
||||
|
||||
import app.cash.turbine.test
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import me.proton.core.account.domain.entity.Account
|
||||
import me.proton.core.accountmanager.domain.AccountManager
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.observability.domain.ObservabilityManager
|
||||
import me.proton.core.plan.domain.entity.DynamicPlan
|
||||
import me.proton.core.plan.domain.usecase.GetDynamicPlans
|
||||
import me.proton.core.plan.presentation.viewmodel.DynamicPlanListViewModel.Action
|
||||
import me.proton.core.plan.presentation.viewmodel.DynamicPlanListViewModel.State
|
||||
import me.proton.core.test.kotlin.CoroutinesTest
|
||||
import me.proton.core.user.domain.UserManager
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
|
||||
class DynamicPlanListViewModelTest : CoroutinesTest by CoroutinesTest() {
|
||||
|
||||
private val userId1 = UserId("userId")
|
||||
private val userId2 = UserId("another")
|
||||
private val userIdAbsent = UserId("absent")
|
||||
private val mutablePrimaryUserIdFlow = MutableStateFlow<UserId?>(userId1)
|
||||
|
||||
private val dynamicPlan = mockk<DynamicPlan> {
|
||||
every { instances } returns emptyMap()
|
||||
}
|
||||
private val plans = listOf(dynamicPlan)
|
||||
private val getDynamicPlans = mockk<GetDynamicPlans> {
|
||||
coEvery { this@mockk.invoke(any()) } returns plans
|
||||
}
|
||||
private val observabilityManager = mockk<ObservabilityManager>(relaxed = true)
|
||||
private val userManagerManager = mockk<UserManager>(relaxed = true) {
|
||||
coEvery { this@mockk.observeUser(any()) } answers {
|
||||
flowOf(
|
||||
when (firstArg<UserId>()) {
|
||||
userId1 -> mockk {
|
||||
every { userId } returns userId1
|
||||
every { currency } returns "CHF"
|
||||
}
|
||||
|
||||
userId2 -> mockk {
|
||||
every { userId } returns userId2
|
||||
every { currency } returns "USD"
|
||||
}
|
||||
|
||||
userIdAbsent -> null
|
||||
else -> null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
private val accountManager = mockk<AccountManager>(relaxed = true) {
|
||||
coEvery { this@mockk.getPrimaryUserId() } returns mutablePrimaryUserIdFlow
|
||||
coEvery { this@mockk.getAccount(any()) } answers {
|
||||
flowOf(
|
||||
when (firstArg<UserId>()) {
|
||||
userId1 -> mockk<Account> { every { userId } returns userId1 }
|
||||
userId2 -> mockk<Account> { every { userId } returns userId2 }
|
||||
userIdAbsent -> null
|
||||
else -> null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var tested: DynamicPlanListViewModel
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
tested = DynamicPlanListViewModel(observabilityManager, userManagerManager, accountManager, getDynamicPlans)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get plans happy path`() = coroutinesTest {
|
||||
// WHEN
|
||||
tested.state.test {
|
||||
// THEN
|
||||
assertIs<State.Loading>(awaitItem())
|
||||
val state = awaitItem()
|
||||
assertIs<State.Success>(state)
|
||||
assertContentEquals(plans, state.plans)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get plans error`() = coroutinesTest {
|
||||
// GIVEN
|
||||
val apiException = ApiException(ApiResult.Error.Http(500, "Server error"))
|
||||
coEvery { getDynamicPlans(any()) } throws apiException
|
||||
|
||||
// WHEN
|
||||
tested.state.test {
|
||||
// THEN
|
||||
assertIs<State.Loading>(awaitItem())
|
||||
val state = awaitItem()
|
||||
assertIs<State.Error>(state)
|
||||
assertEquals(apiException, state.error)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get plans userId1 give CHF`() = coroutinesTest {
|
||||
// WHEN
|
||||
tested.perform(Action.SetUserId(userId1))
|
||||
tested.state.test {
|
||||
// THEN
|
||||
assertIs<State.Loading>(awaitItem())
|
||||
val state = awaitItem()
|
||||
assertIs<State.Success>(state)
|
||||
assertEquals("CHF", state.filter.currency)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get plans userId2 give USD`() = coroutinesTest {
|
||||
// WHEN
|
||||
tested.perform(Action.SetUserId(userId2))
|
||||
tested.state.test {
|
||||
// THEN
|
||||
assertIs<State.Loading>(awaitItem())
|
||||
val state = awaitItem()
|
||||
assertIs<State.Success>(state)
|
||||
assertEquals("USD", state.filter.currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.plan.presentation.viewmodel
|
||||
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.plan.domain.entity.DynamicPlan
|
||||
import me.proton.core.plan.domain.usecase.GetDynamicPlans
|
||||
import me.proton.core.test.kotlin.CoroutinesTest
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
|
||||
class DynamicPlansViewModelTest : CoroutinesTest by CoroutinesTest() {
|
||||
@MockK
|
||||
private lateinit var getDynamicPlans: GetDynamicPlans
|
||||
|
||||
private lateinit var tested: DynamicPlansViewModel
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
tested = DynamicPlansViewModel(getDynamicPlans)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get plans happy path`() = coroutinesTest {
|
||||
// GIVEN
|
||||
val plans = listOf(mockk<DynamicPlan>())
|
||||
coEvery { getDynamicPlans(any()) } returns plans
|
||||
|
||||
// WHEN
|
||||
tested.loadPlans(UserId("user_id")).join()
|
||||
|
||||
// THEN
|
||||
val state = assertIs<DynamicPlansViewModel.State.PlansLoaded>(tested.state.value)
|
||||
assertContentEquals(plans, state.plans)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get plans error`() = coroutinesTest {
|
||||
// GIVEN
|
||||
val apiException = ApiException(ApiResult.Error.Http(500, "Server error"))
|
||||
coEvery { getDynamicPlans(any()) } throws apiException
|
||||
|
||||
// WHEN
|
||||
tested.loadPlans(UserId("user_id")).join()
|
||||
|
||||
// THEN
|
||||
val state = assertIs<DynamicPlansViewModel.State.Error>(tested.state.value)
|
||||
assertEquals(apiException, state.throwable)
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2aa8655387329f1bc49f4dbcfab65a26a400943f431cbade4a0e1cffcd310f33
|
||||
size 5314
|
||||
oid sha256:ff53a0ab13ee191ba6ccfc2d6efd66956e2315d97285b6446be1bf622c0e81e1
|
||||
size 5321
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6f9d9adbab125d1a90a434e702a156d588bac39abcc3aff8158ad28411133e6f
|
||||
size 5717
|
||||
oid sha256:96c038a24548df8ce4ca6aef7935b3f756948ba6977e4926cb88837989510b0b
|
||||
size 5681
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:de84b329cdb6e6220e0263ffe257c1b9cd3fa457cef7d77a6bd85893b9ba1d84
|
||||
size 30698
|
||||
oid sha256:01293b23a3af1087375c0c0e9b9ee34caedf43a83ce5e3a614f525d61d858095
|
||||
size 30639
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:alpha="0.00" android:color="?attr/colorPrimary" android:state_checked="true" />
|
||||
<item android:alpha="0.08" android:color="?attr/colorOnSurface" app:state_dragged="true" />
|
||||
<item android:alpha="0.08" android:color="?attr/colorOnSurface" android:state_checked="false" app:state_dragged="false" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?attr/colorPrimary" android:state_checked="true"/>
|
||||
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_checked="false"/>
|
||||
</selector>
|
||||
Reference in New Issue
Block a user