-
Notifications
You must be signed in to change notification settings - Fork 925
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update Settings: Update PIR Settings Item (#5397)
Task/Issue URL: https://app.asana.com/0/1207908166761516/1208966262488272/f ### Description Adds support for different PIR (Private Information Removal) subscription states in the settings menu, including active, expired, activating, and hidden states. Introduces a new subscription manager to handle PIR status and updates the UI to reflect these states with appropriate icons and interactions. ### Steps to test this PR Prerequisite: Enable `newSettings` feature toggle Prerequisite: Apply the patch in the [Asana task](https://app.asana.com/0/1207908166761516/1208966262488271/f) Note: Only the VPN item, PIR and possibly the Settings item will be seen, depending on the different states. ITR is not included as part of this PR and so is not visible. _PIR Subscribed_ - [x] In `RealSubscriptions` change ln 77 to `return flowOf(listOf(NetP, PIR))` - [x] Open New Settings - [x] Verify PIR item is visible with colored icon and status is on - [x] Click PIR item - [x] PIR screen should open _Unsubscribed_ - [x] In `SubscriptionManager` change ln 442 to `return SubscriptionStatus.UNKNOWN` - [x] Open New Settings - [x] Verify VPN item is not visible _PIR Expired_ - [x] In `SubscriptionManager` change ln 442 to `return SubscriptionStatus.EXPIRED` - [x] Open New Settings - [x] Verify PIR item is visible with grey icon and is not clickable, and status is off - [x] Click PIR item - [x] Nothing should happen _PPro Activating_ - [x] In `SubscriptionManager` change ln 442 to `return SubscriptionStatus.WAITING` - [x] Open New Settings - [x] Verify PIR item is visible with grey icon and status is off - [x] Click PIR item - [x] Nothing should happen _No Entitlement_ - [x] In `SubscriptionManager` change ln 442 to `return SubscriptionStatus.AUTO_RENEWABLE` - [x] In `RealSubscriptions` change ln 77 to `return flowOf(listOf())` - [x] Open New Settings - [x] Verify PIR item is not visible _Legacy Support_ - [x] Turn off `newSettings` - [ ] Verify old PIR settings works by repeating steps above ### UI changes | Before | After | | --- | --- | | ![old_pir_subscribed_active](https://github.com/user-attachments/assets/c7c47bb7-a9c7-43b8-a70e-24492dd359ec) | ![new_pir_subscribed](https://github.com/user-attachments/assets/1330d724-f488-4516-9999-636c09a9a1d7) | | ![old_pir_expired](https://github.com/user-attachments/assets/db81cc81-82a4-4e86-a635-a3a5b89fb9c6) | ![pir_new_expired](https://github.com/user-attachments/assets/cb13fbab-50a0-43e0-a1cb-3d3cce7da811) | | ![old_pir_activating](https://github.com/user-attachments/assets/fb193871-fd1e-45e6-be2c-806ab81f4063) | ![pir_new_activating](https://github.com/user-attachments/assets/4e847f69-4a90-4440-ac9c-44db516ea45c) | | ![old_pir_no_entitlement](https://github.com/user-attachments/assets/5718eb22-db46-43bc-a810-4572ea7dc1db) | ![pir_new_no_entitlement](https://github.com/user-attachments/assets/69a68ed7-2035-4996-89cc-59bbf1872b25) | | ![old_pir_unsubscribed](https://github.com/user-attachments/assets/91d2fb61-bc49-4735-b83e-c8c6c1744488) | ![pir_unsubscribed](https://github.com/user-attachments/assets/899b1a33-b3c5-4068-ad25-8f5e4e964434) |
- Loading branch information
1 parent
b7af5ce
commit 4b15b55
Showing
10 changed files
with
513 additions
and
32 deletions.
There are no files selected for viewing
60 changes: 60 additions & 0 deletions
60
...ptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/pir/PirSubscriptionManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
...pl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyPirSettingView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/* | ||
* Copyright (c) 2023 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.settings.views | ||
|
||
import android.annotation.SuppressLint | ||
import android.content.Context | ||
import android.util.AttributeSet | ||
import android.widget.FrameLayout | ||
import androidx.lifecycle.ViewModelProvider | ||
import androidx.lifecycle.findViewTreeLifecycleOwner | ||
import androidx.lifecycle.findViewTreeViewModelStoreOwner | ||
import com.duckduckgo.anvil.annotations.InjectWith | ||
import com.duckduckgo.common.ui.view.gone | ||
import com.duckduckgo.common.ui.view.show | ||
import com.duckduckgo.common.ui.viewbinding.viewBinding | ||
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.databinding.LegacyViewPirSettingsBinding | ||
import com.duckduckgo.subscriptions.impl.pir.PirActivity.Companion.PirScreenWithEmptyParams | ||
import com.duckduckgo.subscriptions.impl.settings.views.LegacyPirSettingViewModel.Command | ||
import com.duckduckgo.subscriptions.impl.settings.views.LegacyPirSettingViewModel.Command.OpenPir | ||
import com.duckduckgo.subscriptions.impl.settings.views.LegacyPirSettingViewModel.ViewState | ||
import dagger.android.support.AndroidSupportInjection | ||
import javax.inject.Inject | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.SupervisorJob | ||
import kotlinx.coroutines.cancel | ||
import kotlinx.coroutines.flow.launchIn | ||
import kotlinx.coroutines.flow.onEach | ||
|
||
@InjectWith(ViewScope::class) | ||
class LegacyPirSettingView @JvmOverloads constructor( | ||
context: Context, | ||
attrs: AttributeSet? = null, | ||
defStyle: Int = 0, | ||
) : FrameLayout(context, attrs, defStyle) { | ||
|
||
@Inject | ||
lateinit var viewModelFactory: ViewViewModelFactory | ||
|
||
@Inject | ||
lateinit var globalActivityStarter: GlobalActivityStarter | ||
|
||
private var coroutineScope: CoroutineScope? = null | ||
|
||
private val binding: LegacyViewPirSettingsBinding by viewBinding() | ||
|
||
private val viewModel: LegacyPirSettingViewModel by lazy { | ||
ViewModelProvider(findViewTreeViewModelStoreOwner()!!, viewModelFactory)[LegacyPirSettingViewModel::class.java] | ||
} | ||
|
||
private var job: ConflatedJob = ConflatedJob() | ||
|
||
override fun onAttachedToWindow() { | ||
AndroidSupportInjection.inject(this) | ||
super.onAttachedToWindow() | ||
|
||
findViewTreeLifecycleOwner()?.lifecycle?.addObserver(viewModel) | ||
|
||
binding.pirSettings.setClickListener { | ||
viewModel.onPir() | ||
} | ||
|
||
@SuppressLint("NoHardcodedCoroutineDispatcher") | ||
coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) | ||
|
||
job += viewModel.commands() | ||
.onEach { processCommands(it) } | ||
.launchIn(coroutineScope!!) | ||
|
||
viewModel.viewState | ||
.onEach { renderView(it) } | ||
.launchIn(coroutineScope!!) | ||
} | ||
|
||
override fun onDetachedFromWindow() { | ||
super.onDetachedFromWindow() | ||
findViewTreeLifecycleOwner()?.lifecycle?.removeObserver(viewModel) | ||
coroutineScope?.cancel() | ||
job.cancel() | ||
coroutineScope = null | ||
} | ||
|
||
private fun renderView(viewState: ViewState) { | ||
if (viewState.hasSubscription) { | ||
binding.pirSettings.show() | ||
} else { | ||
binding.pirSettings.gone() | ||
} | ||
} | ||
|
||
private fun processCommands(command: Command) { | ||
when (command) { | ||
is OpenPir -> { | ||
globalActivityStarter.start(context, PirScreenWithEmptyParams) | ||
} | ||
} | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
...c/main/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyPirSettingViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* Copyright (c) 2023 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.settings.views | ||
|
||
import android.annotation.SuppressLint | ||
import androidx.lifecycle.DefaultLifecycleObserver | ||
import androidx.lifecycle.LifecycleOwner | ||
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.pixels.SubscriptionPixelSender | ||
import com.duckduckgo.subscriptions.impl.settings.views.LegacyPirSettingViewModel.Command.OpenPir | ||
import javax.inject.Inject | ||
import kotlinx.coroutines.channels.BufferOverflow | ||
import kotlinx.coroutines.channels.Channel | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.flow.launchIn | ||
import kotlinx.coroutines.flow.onEach | ||
import kotlinx.coroutines.flow.receiveAsFlow | ||
import kotlinx.coroutines.launch | ||
|
||
@SuppressLint("NoLifecycleObserver") // we don't observe app lifecycle | ||
@ContributesViewModel(ViewScope::class) | ||
class LegacyPirSettingViewModel @Inject constructor( | ||
private val subscriptions: Subscriptions, | ||
private val pixelSender: SubscriptionPixelSender, | ||
) : ViewModel(), DefaultLifecycleObserver { | ||
|
||
sealed class Command { | ||
data object OpenPir : Command() | ||
} | ||
|
||
private val command = Channel<Command>(1, BufferOverflow.DROP_OLDEST) | ||
internal fun commands(): Flow<Command> = command.receiveAsFlow() | ||
data class ViewState(val hasSubscription: Boolean = false) | ||
|
||
private val _viewState = MutableStateFlow(ViewState()) | ||
val viewState = _viewState.asStateFlow() | ||
|
||
fun onPir() { | ||
pixelSender.reportAppSettingsPirClick() | ||
sendCommand(OpenPir) | ||
} | ||
|
||
override fun onCreate(owner: LifecycleOwner) { | ||
super.onCreate(owner) | ||
subscriptions.getEntitlementStatus().onEach { | ||
_viewState.emit(viewState.value.copy(hasSubscription = it.contains(PIR))) | ||
}.launchIn(viewModelScope) | ||
} | ||
|
||
private fun sendCommand(newCommand: Command) { | ||
viewModelScope.launch { | ||
command.send(newCommand) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.