feat(plan): Add dynamic storage title and icon to current subscription.

This commit is contained in:
dkadrikj
2024-01-31 12:02:39 +01:00
parent c6b0260e99
commit 727a2125af
19 changed files with 160 additions and 59 deletions
+9 -5
View File
@@ -176,22 +176,26 @@ public final class me/proton/core/plan/data/api/response/DynamicEntitlementResou
public final class me/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress : me/proton/core/plan/data/api/response/DynamicEntitlementResource {
public static final field Companion Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress$Companion;
public synthetic fun <init> (ILjava/lang/String;JJJLjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;JJJLjava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;JJJLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (ILjava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()J
public final fun component3 ()J
public final fun component4 ()J
public final fun component5 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;JJJLjava/lang/String;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress;
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress;Ljava/lang/String;JJJLjava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress;
public final fun component6 ()Ljava/lang/String;
public final fun component7 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress;
public static synthetic fun copy$default (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress;Ljava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress;
public fun equals (Ljava/lang/Object;)Z
public final fun getCurrent ()J
public final fun getIconName ()Ljava/lang/String;
public final fun getMax ()J
public final fun getMin ()J
public final fun getTag ()Ljava/lang/String;
public final fun getText ()Ljava/lang/String;
public final fun getTitle ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public static final synthetic fun write$Self (Lme/proton/core/plan/data/api/response/DynamicEntitlementResource$Progress;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
@@ -60,7 +60,13 @@ sealed class DynamicEntitlementResource {
val max: Long,
@SerialName("Tag")
val tag: String? = null
val tag: String? = null,
@SerialName("Title")
val title: String? = null,
@SerialName("IconName")
val iconName: String? = null
) : DynamicEntitlementResource()
@Serializable
@@ -83,7 +89,9 @@ fun DynamicEntitlementResource.toDynamicPlanEntitlement(iconsEndpoint: String):
current = current,
min = min,
max = max,
tag = tag
tag = tag,
title = title ?: "",
iconUrl = if (!iconName.isNullOrEmpty()) "$iconsEndpoint/$iconName".replace("//", "/") else null
)
is DynamicEntitlementResource.Unknown -> null
@@ -122,7 +122,9 @@ class DynamicEntitlementResourceTest {
current = 128,
min = 0,
max = 1024,
tag = DynamicEntitlement.Progress.Base
tag = DynamicEntitlement.Progress.Base,
title = "",
iconUrl = null
),
DynamicEntitlementResource.Progress(
text = "128 MB on 1GB",
@@ -85,6 +85,18 @@ class ObserveUserCurrencyTest : CoroutinesTest by CoroutinesTest() {
assertEquals(expected = "USD", actual = tested.defaultCurrency)
}
@Test
fun returnCurrenciesForNoUser() = runTest {
// Given
val currency = "USD"
// When
tested.invoke(null).test {
// Then
assertEquals(expected = currency, actual = awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun returnCurrenciesForUser1() = runTest {
// Given
@@ -21,6 +21,7 @@ package me.proton.core.plan.data.usecase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
@@ -34,6 +35,7 @@ import me.proton.core.payment.domain.entity.Currency
import me.proton.core.payment.domain.entity.PaymentTokenEntity
import me.proton.core.payment.domain.entity.ProtonPaymentToken
import me.proton.core.payment.domain.entity.SubscriptionCycle
import me.proton.core.payment.domain.usecase.AcknowledgeGooglePlayPurchase
import me.proton.core.plan.domain.entity.Subscription
import me.proton.core.plan.domain.entity.SubscriptionManagement
import me.proton.core.plan.domain.repository.PlansRepository
@@ -336,4 +338,31 @@ class PerformSubscribeTest {
coVerify(exactly = 0) { humanVerificationManager.clearDetails(any()) }
}
@Test
fun `optional acknowledge purchase present successful subscription`() = runTest {
val acknowledgeGooglePlayPurchase = mockk<AcknowledgeGooglePlayPurchase>(relaxed = true)
val acknowledgeGooglePlayPurchaseOptional = mockk<Optional<AcknowledgeGooglePlayPurchase>>(relaxed = true)
every { acknowledgeGooglePlayPurchaseOptional.isPresent } returns true
every { acknowledgeGooglePlayPurchaseOptional.get() } returns acknowledgeGooglePlayPurchase
useCase = PerformSubscribeImpl(
acknowledgeGooglePlayPurchaseOptional,
repository,
humanVerificationManager,
clientIdProvider
)
useCase.invoke(
userId = testUserId,
amount = 1,
currency = Currency.CHF,
cycle = SubscriptionCycle.YEARLY,
planNames = listOf(testPlanName),
codes = null,
paymentToken = testPaymentToken,
subscriptionManagement = SubscriptionManagement.GOOGLE_MANAGED
)
coVerify(exactly = 1) { humanVerificationManager.clearDetails(any()) }
coVerify(exactly = 1) { acknowledgeGooglePlayPurchase.invoke(testPaymentToken) }
}
}
+7 -3
View File
@@ -103,20 +103,24 @@ public final class me/proton/core/plan/domain/entity/DynamicEntitlement$Progress
public static final field Base Ljava/lang/String;
public static final field Drive Ljava/lang/String;
public static final field Tag Lme/proton/core/plan/domain/entity/DynamicEntitlement$Progress$Tag;
public fun <init> (Ljava/lang/String;JJJLjava/lang/String;)V
public fun <init> (Ljava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()J
public final fun component3 ()J
public final fun component4 ()J
public final fun component5 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;JJJLjava/lang/String;)Lme/proton/core/plan/domain/entity/DynamicEntitlement$Progress;
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicEntitlement$Progress;Ljava/lang/String;JJJLjava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicEntitlement$Progress;
public final fun component6 ()Ljava/lang/String;
public final fun component7 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lme/proton/core/plan/domain/entity/DynamicEntitlement$Progress;
public static synthetic fun copy$default (Lme/proton/core/plan/domain/entity/DynamicEntitlement$Progress;Ljava/lang/String;JJJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/plan/domain/entity/DynamicEntitlement$Progress;
public fun equals (Ljava/lang/Object;)Z
public final fun getCurrent ()J
public final fun getIconUrl ()Ljava/lang/String;
public final fun getMax ()J
public final fun getMin ()J
public final fun getTag ()Ljava/lang/String;
public final fun getText ()Ljava/lang/String;
public final fun getTitle ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -30,7 +30,9 @@ sealed class DynamicEntitlement {
val current: Long,
val min: Long,
val max: Long,
val tag: String?
val tag: String?,
val title: String,
val iconUrl: String?
) : DynamicEntitlement() {
companion object Tag {
const val Base: String = "base"
+3 -4
View File
@@ -203,6 +203,7 @@ public final class me/proton/core/plan/presentation/databinding/DynamicEntitleme
}
public final class me/proton/core/plan/presentation/databinding/DynamicEntitlementStorageViewBinding : androidx/viewbinding/ViewBinding {
public final field icon Landroidx/appcompat/widget/AppCompatImageView;
public final field progress Lcom/google/android/material/progressindicator/LinearProgressIndicator;
public final field tagText Landroid/widget/TextView;
public final field text Landroid/widget/TextView;
@@ -1511,7 +1512,6 @@ public final class me/proton/core/plan/presentation/usecase/StorageUsageState$Ne
}
public final class me/proton/core/plan/presentation/view/DynamicEntitlementDescriptionView : androidx/constraintlayout/widget/ConstraintLayout {
public static final field Companion Lme/proton/core/plan/presentation/view/DynamicEntitlementDescriptionView$Companion;
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
@@ -1523,9 +1523,6 @@ public final class me/proton/core/plan/presentation/view/DynamicEntitlementDescr
public final fun setText (Ljava/lang/CharSequence;)V
}
public final class me/proton/core/plan/presentation/view/DynamicEntitlementDescriptionView$Companion {
}
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;
}
@@ -1536,11 +1533,13 @@ public final class me/proton/core/plan/presentation/view/DynamicEntitlementProgr
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;I)V
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;II)V
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;IIILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getIcon ()Ljava/lang/String;
public final fun getProgress ()I
public final fun getProgressMax ()I
public final fun getProgressMin ()I
public final fun getTagText ()Ljava/lang/CharSequence;
public final fun getText ()Ljava/lang/CharSequence;
public final fun setIcon (Ljava/lang/String;)V
public final fun setProgress (III)V
public static synthetic fun setProgress$default (Lme/proton/core/plan/presentation/view/DynamicEntitlementProgressView;IIIILjava/lang/Object;)V
public final fun setTagText (Ljava/lang/CharSequence;)V
+1 -1
View File
@@ -31,7 +31,7 @@ protonBuild {
protonCoverage {
branchCoveragePercentage.set(50)
lineCoveragePercentage.set(56)
lineCoveragePercentage.set(55)
}
publishOption.shouldBePublishedAsLib = true
@@ -33,13 +33,9 @@ fun DynamicEntitlement.toView(context: Context) = when (this) {
}
is DynamicEntitlement.Progress -> DynamicEntitlementProgressView(context).apply {
// TODO the tagText will directly come from BE
tagText = when (this@toView.tag) {
DynamicEntitlement.Progress.Base -> context.getString(R.string.plan_entitlement_tag_base)
DynamicEntitlement.Progress.Drive -> context.getString(R.string.plan_entitlement_tag_drive)
else -> null
}
tagText = this@toView.title
text = this@toView.text
icon = this@toView.iconUrl
setProgress(
min = this@toView.min.div(1000000).toInt(),
max = this@toView.max.div(1000000).toInt(),
@@ -9,11 +9,9 @@ import android.view.LayoutInflater
import androidx.annotation.DrawableRes
import androidx.annotation.StyleRes
import androidx.constraintlayout.widget.ConstraintLayout
import coil.ImageLoader
import coil.decode.SvgDecoder
import coil.load
import me.proton.core.plan.presentation.R
import me.proton.core.plan.presentation.databinding.DynamicEntitlementDescriptionViewBinding.inflate
import me.proton.core.plan.presentation.view.EntitlementImageLoader.loadIcon
import okhttp3.HttpUrl
import java.io.File
import java.nio.ByteBuffer
@@ -42,7 +40,9 @@ class DynamicEntitlementDescriptionView @JvmOverloads constructor(
*/
var icon: Any? = null
set(value) = with(binding) {
icon.load(value, getImageLoader(context)) { fallback(R.drawable.ic_proton_checkmark) }
icon.loadIcon(value, context) {
it.fallback(R.drawable.ic_proton_checkmark)
}
}
var text: CharSequence?
@@ -50,12 +50,4 @@ class DynamicEntitlementDescriptionView @JvmOverloads constructor(
set(value) {
binding.text.text = value
}
companion object {
private var imageLoader: ImageLoader? = null
private fun getImageLoader(context: Context) = imageLoader ?: ImageLoader.Builder(context)
.components { add(SvgDecoder.Factory()) }
.build()
.apply { imageLoader = this }
}
}
@@ -9,9 +9,9 @@ import androidx.annotation.StyleRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat.getColor
import androidx.core.content.ContextCompat.getColorStateList
import androidx.core.content.ContextCompat.getDrawable
import me.proton.core.plan.presentation.R
import me.proton.core.plan.presentation.databinding.DynamicEntitlementStorageViewBinding.inflate
import me.proton.core.plan.presentation.view.EntitlementImageLoader.loadIcon
@Suppress("MagicNumber")
class DynamicEntitlementProgressView @JvmOverloads constructor(
@@ -35,6 +35,8 @@ class DynamicEntitlementProgressView @JvmOverloads constructor(
binding.text.text = value
}
var icon: String? = null
var progress: Int
get() = binding.progress.progress
private set(value) {
@@ -86,15 +88,11 @@ class DynamicEntitlementProgressView @JvmOverloads constructor(
private fun updateTextColors() {
if (percentage >= STORAGE_ERROR_THRESHOLD) {
val errorColor = getColor(context, R.color.notification_error)
val exclamation =
getDrawable(context, R.drawable.ic_proton_exclamation_circle_filled)
exclamation?.setTint(errorColor)
binding.tagText.setCompoundDrawablesRelativeWithIntrinsicBounds(
null,
null,
exclamation,
null
)
icon?.let {
binding.icon.loadIcon(it, context)
} ?: run { binding.icon.setImageResource(0) }
binding.text.setTextColor(errorColor)
} else {
binding.tagText.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null)
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2024 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.widget.ImageView
import coil.ImageLoader
import coil.decode.SvgDecoder
import coil.load
import coil.request.ImageRequest
internal object EntitlementImageLoader {
private var imageLoader: ImageLoader? = null
private fun getImageLoader(context: Context) = imageLoader ?: ImageLoader.Builder(context)
.components { add(SvgDecoder.Factory()) }
.build()
.apply { imageLoader = this }
internal fun ImageView.loadIcon(
data: Any?,
context: Context,
fallback: ((ImageRequest.Builder) -> Unit)? = null
) {
load(data, getImageLoader(context)) {
if (fallback != null) {
fallback(this)
}
}
}
}
@@ -25,6 +25,18 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="Drive storage" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="@dimen/default_margin"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="@id/tagText"
app:layout_constraintStart_toEndOf="@id/tagText"
app:layout_constraintTop_toTopOf="@id/tagText"
app:tint="?proton_notification_error"
tools:srcCompat="@drawable/ic_proton_exclamation_circle" />
<TextView
android:id="@+id/text"
style="@style/Proton.Text.Caption"
@@ -33,7 +45,7 @@
android:gravity="end"
app:layout_constraintBaseline_toBaselineOf="@id/tagText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tagText"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="162 MB of 3 TB" />
@@ -164,11 +164,6 @@
<string name="ten_hide_my_email_aliases">10 Hide My Email aliases</string>
<!-- endregion -->
<!-- region Plan entitlement tags -->
<string name="plan_entitlement_tag_base">Mail storage</string>
<string name="plan_entitlement_tag_drive">Drive storage</string>
<!-- endregion -->
<!-- region Plan entitlement tags -->
<string name="upgrade_storage_current_drive_storage">Drive storage: %d%% full</string>
<string name="upgrade_storage_current_mail_storage">Mail storage: %d%% full</string>
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a65bc5bb17ec009e7f87c6d3294d65e397b5b9ab6c0130f4949454c143a448c
size 7674
oid sha256:e7182625147ea60fb074a7e1cc0307c05e73f53d5d181de7b568753056aac3db
size 7733
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f73940baab6ca6231151a6b3be1ed3e00f8892b6616c72996a9694e0733fa50a
size 8710
oid sha256:e2111b85c5b7c07896c98396d80029143e1cf59b6cc787a42b246bfce3eafda1
size 8141
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2043be80addabfca8762a97ed89957c0c46830b82aa37e5d5455dbc42e8d355a
size 7823
oid sha256:734b7ce225c499e132382b06afa622ec16adeaf4e71454f949e58b2ff93941b8
size 7884
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ee6393b5cc9b42302e45246d2ab08c7f46973dde0b71a43897808d24dc620ccf
size 32215
oid sha256:044916a325f88e9a32227618491af99969e4f912b5d86d7ebdfd8746a4843790
size 31903