Skip to content

Commit

Permalink
create PirSubscriptionManager and use states to render view
Browse files Browse the repository at this point in the history
The PirSubscriptionManager mimics our Manager for the VPN, exposing the status of PIR so we can render the viewstates correctly
  • Loading branch information
mikescamell committed Dec 18, 2024
1 parent da4a323 commit 8b82a19
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -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<PirStatus>

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<PirStatus> = hasPirEntitlement().map { getPirStatusInternal(it) }

private fun hasPirEntitlement(): Flow<Boolean> = 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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -74,10 +81,6 @@ class PirSettingView @JvmOverloads constructor(

findViewTreeLifecycleOwner()?.lifecycle?.addObserver(viewModel)

binding.pirSettings.setClickListener {
viewModel.onPir()
}

@SuppressLint("NoHardcodedCoroutineDispatcher")
coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

Expand All @@ -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
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {

Expand All @@ -51,7 +59,16 @@ class PirSettingViewModel @Inject constructor(

private val command = Channel<Command>(1, BufferOverflow.DROP_OLDEST)
internal fun commands(): Flow<Command> = 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()
Expand All @@ -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)
}

Expand Down

0 comments on commit 8b82a19

Please sign in to comment.