From 8b82a194958485e763e742fc765477110421918b Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Mon, 16 Dec 2024 12:57:05 +0100 Subject: [PATCH] create PirSubscriptionManager and use states to render view The PirSubscriptionManager mimics our Manager for the VPN, exposing the status of PIR so we can render the viewstates correctly --- .../impl/pir/PirSubscriptionManager.kt | 60 +++++++++++++++++++ .../impl/settings/views/PirSettingView.kt | 32 +++++++--- .../settings/views/PirSettingViewModel.kt | 37 ++++++++++-- 3 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/pir/PirSubscriptionManager.kt diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/pir/PirSubscriptionManager.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/pir/PirSubscriptionManager.kt new file mode 100644 index 000000000000..52d213cc6ef6 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/pir/PirSubscriptionManager.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.subscriptions.impl.pir + +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.subscriptions.api.Product.PIR +import com.duckduckgo.subscriptions.api.SubscriptionStatus +import com.duckduckgo.subscriptions.api.Subscriptions +import com.duckduckgo.subscriptions.impl.pir.PirSubscriptionManager.PirStatus +import com.squareup.anvil.annotations.ContributesBinding +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +interface PirSubscriptionManager { + fun pirStatus(): Flow + + enum class PirStatus { + ACTIVE, + EXPIRED, + SIGNED_OUT, + INACTIVE, + WAITING, + INELIGIBLE, + } +} + +@ContributesBinding(AppScope::class) +class RealPirSubscriptionManager @Inject constructor( + private val subscriptions: Subscriptions, +) : PirSubscriptionManager { + + override fun pirStatus(): Flow = hasPirEntitlement().map { getPirStatusInternal(it) } + + private fun hasPirEntitlement(): Flow = subscriptions.getEntitlementStatus().map { it.contains(PIR) } + + private suspend fun getPirStatusInternal(hasValidEntitlement: Boolean): PirStatus = when { + !hasValidEntitlement -> PirStatus.INELIGIBLE + else -> when (subscriptions.getSubscriptionStatus()) { + SubscriptionStatus.INACTIVE, SubscriptionStatus.EXPIRED -> PirStatus.EXPIRED + SubscriptionStatus.UNKNOWN -> PirStatus.SIGNED_OUT + SubscriptionStatus.AUTO_RENEWABLE, SubscriptionStatus.NOT_AUTO_RENEWABLE, SubscriptionStatus.GRACE_PERIOD -> PirStatus.ACTIVE + SubscriptionStatus.WAITING -> PirStatus.WAITING + } + } +} diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/PirSettingView.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/PirSettingView.kt index 17c9316993ec..8db8703c2126 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/PirSettingView.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/PirSettingView.kt @@ -20,6 +20,8 @@ import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import android.widget.FrameLayout +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeViewModelStoreOwner @@ -31,11 +33,16 @@ import com.duckduckgo.common.utils.ConflatedJob import com.duckduckgo.common.utils.ViewViewModelFactory import com.duckduckgo.di.scopes.ViewScope import com.duckduckgo.navigation.api.GlobalActivityStarter +import com.duckduckgo.subscriptions.impl.R import com.duckduckgo.subscriptions.impl.databinding.ViewPirSettingsBinding import com.duckduckgo.subscriptions.impl.pir.PirActivity.Companion.PirScreenWithEmptyParams import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.Command import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.Command.OpenPir import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.ViewState +import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.ViewState.PirState.Activating +import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.ViewState.PirState.Expired +import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.ViewState.PirState.Hidden +import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.ViewState.PirState.Subscribed import dagger.android.support.AndroidSupportInjection import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -74,10 +81,6 @@ class PirSettingView @JvmOverloads constructor( findViewTreeLifecycleOwner()?.lifecycle?.addObserver(viewModel) - binding.pirSettings.setClickListener { - viewModel.onPir() - } - @SuppressLint("NoHardcodedCoroutineDispatcher") coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) @@ -99,10 +102,23 @@ class PirSettingView @JvmOverloads constructor( } private fun renderView(viewState: ViewState) { - if (viewState.hasSubscription) { - binding.pirSettings.show() - } else { - binding.pirSettings.gone() + with(binding.pirSettings) { + when (viewState.pirState) { + is Subscribed -> { + isVisible = true + setStatus(isOn = true) + setLeadingIconResource(R.drawable.ic_identity_blocked_pir_color_24) + isClickable = true + binding.pirSettings.setClickListener { viewModel.onPir() } + } + Expired, Activating -> { + isVisible = true + isClickable = false + setStatus(isOn = false) + setLeadingIconResource(R.drawable.ic_identity_blocked_pir_grayscale_color_24) + } + Hidden -> isGone = true + } } } diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/PirSettingViewModel.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/PirSettingViewModel.kt index 43ae9e425988..9f2220c295b2 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/PirSettingViewModel.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/PirSettingViewModel.kt @@ -23,10 +23,17 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.duckduckgo.anvil.annotations.ContributesViewModel import com.duckduckgo.di.scopes.ViewScope -import com.duckduckgo.subscriptions.api.Product.PIR -import com.duckduckgo.subscriptions.api.Subscriptions +import com.duckduckgo.subscriptions.impl.pir.PirSubscriptionManager +import com.duckduckgo.subscriptions.impl.pir.PirSubscriptionManager.PirStatus.ACTIVE +import com.duckduckgo.subscriptions.impl.pir.PirSubscriptionManager.PirStatus.EXPIRED +import com.duckduckgo.subscriptions.impl.pir.PirSubscriptionManager.PirStatus.INACTIVE +import com.duckduckgo.subscriptions.impl.pir.PirSubscriptionManager.PirStatus.INELIGIBLE +import com.duckduckgo.subscriptions.impl.pir.PirSubscriptionManager.PirStatus.SIGNED_OUT +import com.duckduckgo.subscriptions.impl.pir.PirSubscriptionManager.PirStatus.WAITING import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.Command.OpenPir +import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.ViewState.PirState +import com.duckduckgo.subscriptions.impl.settings.views.PirSettingViewModel.ViewState.PirState.Hidden import javax.inject.Inject import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel @@ -36,12 +43,13 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @SuppressLint("NoLifecycleObserver") // we don't observe app lifecycle @ContributesViewModel(ViewScope::class) class PirSettingViewModel @Inject constructor( - private val subscriptions: Subscriptions, + private val pirSubscriptionManager: PirSubscriptionManager, private val pixelSender: SubscriptionPixelSender, ) : ViewModel(), DefaultLifecycleObserver { @@ -51,7 +59,16 @@ class PirSettingViewModel @Inject constructor( private val command = Channel(1, BufferOverflow.DROP_OLDEST) internal fun commands(): Flow = command.receiveAsFlow() - data class ViewState(val hasSubscription: Boolean = false) + data class ViewState(val pirState: PirState = Hidden) { + + sealed class PirState { + + data object Hidden : PirState() + data object Subscribed : PirState() + data object Expired : PirState() + data object Activating : PirState() + } + } private val _viewState = MutableStateFlow(ViewState()) val viewState = _viewState.asStateFlow() @@ -63,8 +80,16 @@ class PirSettingViewModel @Inject constructor( override fun onCreate(owner: LifecycleOwner) { super.onCreate(owner) - subscriptions.getEntitlementStatus().onEach { - _viewState.emit(viewState.value.copy(hasSubscription = it.contains(PIR))) + + pirSubscriptionManager.pirStatus().onEach { status -> + val pirState = when (status) { + ACTIVE -> PirState.Subscribed + INACTIVE, EXPIRED -> PirState.Expired + WAITING -> PirState.Activating + SIGNED_OUT, INELIGIBLE -> PirState.Hidden + } + + _viewState.update { it.copy(pirState = pirState) } }.launchIn(viewModelScope) }