From cc21a12f8f3d4bce0e930e1f2887680e861f70a7 Mon Sep 17 00:00:00 2001 From: Mike Scamell Date: Fri, 13 Dec 2024 16:16:26 +0100 Subject: [PATCH] Update Settings: Update Ppro Purchasing and Setting items (#5385) Task/Issue URL: https://app.asana.com/0/1207908166761516/1208966262488270/f ### Description Initial changes to start moving Ppro settings to new Settings world. This PR focuses specifically on the Purchasing state as well as the Subscription Setting item state, as these are part of the same plugin. [Designs](https://www.figma.com/design/CjH849hL53lhsPlf6Ufeo4/%E2%9A%99%EF%B8%8F-Browser-Settings-Documentation-(All-Platforms)?node-id=7605-431390&node-type=instance&t=ZKIMXkdZqzh5gEjU-11) ### Steps to test this PR Prerequisite: Enable `newSettings` feature toggle Prerequisite: Apply the patch in the [Asana task](https://app.asana.com/0/1207908166761516/1208966262488270/f) Also remember VPN, PIR, and ITR are **not** changed in this PR ### _Not purchased_ - [x] Set ln 99 of `ProSettingViewModel` to `_viewState.emit(viewState.value.copy(status = EXPIRED, region = SubscriptionRegion.US))` - [x] Open Settings - [x] Check against [designs](https://www.figma.com/design/CjH849hL53lhsPlf6Ufeo4/%E2%9A%99%EF%B8%8F-Browser-Settings-Documentation-(All-Platforms)?node-id=7605-431390&node-type=instance&t=ZKIMXkdZqzh5gEjU-11) - [x] Click items - [x] Ensure correct screens are open | Before | After | | ------ | ----- | ![not_purchased_original](https://github.com/user-attachments/assets/bfc77f48-77f2-49f6-96fd-c650b082fa15)|![Screenshot_20241212_192120](https://github.com/user-attachments/assets/316945a7-b5ae-4777-aa37-05822f19ec0b)| ### _Purchased_ - [x] Set ln 99 of `ProSettingViewModel` to `_viewState.emit(viewState.value.copy(status = AUTO_RENEWABLE, region = SubscriptionRegion.US))` - [x] Open Settings - [x] Check against [designs](https://www.figma.com/design/CjH849hL53lhsPlf6Ufeo4/%E2%9A%99%EF%B8%8F-Browser-Settings-Documentation-(All-Platforms)?node-id=7605-431390&node-type=instance&t=ZKIMXkdZqzh5gEjU-11) - [x] Click Subscription Settings - [x] Ensure Subscription Settings is opened | Before | After | | ------ | ----- | ![purchased_original](https://github.com/user-attachments/assets/d0c244c0-5535-4adc-944a-a324a8f1eda7)|![subscribed](https://github.com/user-attachments/assets/459f5e63-f161-4809-b25c-d01422ebfcfa)| ### _Activating (Waiting)_ - [x] Set ln 99 of `ProSettingViewModel` to `_viewState.emit(viewState.value.copy(status = WAITING, region = SubscriptionRegion.US))` - [x] Open Settings - [x] Check against [designs](https://www.figma.com/design/CjH849hL53lhsPlf6Ufeo4/%E2%9A%99%EF%B8%8F-Browser-Settings-Documentation-(All-Platforms)?node-id=7605-431390&node-type=instance&t=ZKIMXkdZqzh5gEjU-11) - [x] Click Subscription Settings - [x] Ensure Subscription Settings is opened | Before | After | | ------ | ----- | ![original_waiting](https://github.com/user-attachments/assets/05934c81-c3da-4b40-bfd7-c453f07f919e)|![activating](https://github.com/user-attachments/assets/40ec262f-b764-44f0-9d80-eaabb5ce6f73)| ### _Expired_ - [x] Set ln 99 of `ProSettingViewModel` to `_viewState.emit(viewState.value.copy(status = EXPIRED, region = SubscriptionRegion.US))` - [x] Open Settings - [x] Check against [designs](https://www.figma.com/design/CjH849hL53lhsPlf6Ufeo4/%E2%9A%99%EF%B8%8F-Browser-Settings-Documentation-(All-Platforms)?node-id=7605-431390&node-type=instance&t=ZKIMXkdZqzh5gEjU-11) - [x] Click Subscription Settings - [x] Ensure Subscription Settings is opened | Before | After | | ------ | ----- | ![original_expired](https://github.com/user-attachments/assets/3d26f2b7-382c-4d2a-b5ee-3c16b28e413f)|![expired](https://github.com/user-attachments/assets/3972412e-6daf-4a10-99b1-48f85d104914)| --- .../app/settings/NewSettingsActivity.kt | 2 +- .../main/res/layout/content_settings_new.xml | 13 +- .../ui/view/expand/DaxExpandableMenuItem.kt | 10 +- .../ui/view/listitem/BookmarksListItem.kt | 2 +- .../common/ui/view/listitem/DaxListItem.kt | 21 +- .../ui/view/listitem/OneLineListItem.kt | 8 +- .../ui/view/listitem/SettingsListItem.kt | 19 +- .../ui/view/listitem/TwoLineListItem.kt | 8 +- .../res/layout/component_two_line_item.xml | 26 ++- .../src/main/res/values/attrs-lists.xml | 7 + .../src/main/res/drawable/ic_vpn_color_24.xml | 16 ++ .../drawable/ic_vpn_grayscale_color_24.xml | 16 ++ .../settings/plugins/SubsSettingsPlugins.kt | 10 +- .../settings/views/LegacyProSettingView.kt | 196 ++++++++++++++++++ .../views/LegacyProSettingViewModel.kt | 105 ++++++++++ .../impl/settings/views/ProSettingView.kt | 78 ++++--- .../ic_identity_blocked_pir_color_24.xml | 0 ...dentity_blocked_pir_grayscale_color_24.xml | 0 ...ic_identity_theft_restoration_color_24.xml | 0 ...y_theft_restoration_grayscale_color_24.xml | 0 .../res/drawable/ic_privacy_pro_color_24.xml | 0 .../main/res/layout/legacy_view_settings.xml | 110 ++++++++++ .../src/main/res/layout/view_settings.xml | 37 ++-- .../src/main/res/values/donottranslate.xml | 22 ++ .../LegacyLegacyProSettingViewModelTest.kt | 82 ++++++++ 25 files changed, 695 insertions(+), 93 deletions(-) rename {common/common-ui => network-protection/network-protection-impl}/src/main/res/drawable/ic_vpn_color_24.xml (81%) rename {common/common-ui => network-protection/network-protection-impl}/src/main/res/drawable/ic_vpn_grayscale_color_24.xml (81%) create mode 100644 subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyProSettingView.kt create mode 100644 subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyProSettingViewModel.kt rename {common/common-ui => subscriptions/subscriptions-impl}/src/main/res/drawable/ic_identity_blocked_pir_color_24.xml (100%) rename {common/common-ui => subscriptions/subscriptions-impl}/src/main/res/drawable/ic_identity_blocked_pir_grayscale_color_24.xml (100%) rename {common/common-ui => subscriptions/subscriptions-impl}/src/main/res/drawable/ic_identity_theft_restoration_color_24.xml (100%) rename {common/common-ui => subscriptions/subscriptions-impl}/src/main/res/drawable/ic_identity_theft_restoration_grayscale_color_24.xml (100%) rename {common/common-ui => subscriptions/subscriptions-impl}/src/main/res/drawable/ic_privacy_pro_color_24.xml (100%) create mode 100644 subscriptions/subscriptions-impl/src/main/res/layout/legacy_view_settings.xml create mode 100644 subscriptions/subscriptions-impl/src/main/res/values/donottranslate.xml create mode 100644 subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyLegacyProSettingViewModelTest.kt diff --git a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt index 23c911930f0b..acded73d4e11 100644 --- a/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/settings/NewSettingsActivity.kt @@ -153,7 +153,7 @@ class NewSettingsActivity : DuckDuckGoActivity() { get() = binding.includeSettings.contentSettingsInternal private val viewsPro - get() = binding.includeSettings.settingsSectionPro + get() = binding.includeSettings.contentSettingsPrivacyPro override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/res/layout/content_settings_new.xml b/app/src/main/res/layout/content_settings_new.xml index 4b0f1d6b3cd4..d8c6f6803545 100644 --- a/app/src/main/res/layout/content_settings_new.xml +++ b/app/src/main/res/layout/content_settings_new.xml @@ -36,13 +36,14 @@ android:layout_marginTop="@dimen/keyline_4" /> + android:id="@+id/contentSettingsPrivacyPro" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" > + + android:layout_width="match_parent" + android:layout_height="wrap_content" /> Small @@ -306,7 +313,7 @@ abstract class DaxListItem( } } - fun dimension(size: LeadingIconSize): Int { + fun dimension(size: IconSize): Int { return when (size) { Small -> R.dimen.listItemImageSmallSize Medium -> R.dimen.listItemImageMediumSize diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/OneLineListItem.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/OneLineListItem.kt index 9971561f021f..f26fc192ffc3 100644 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/OneLineListItem.kt +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/OneLineListItem.kt @@ -21,7 +21,7 @@ import android.util.AttributeSet import android.view.View import android.widget.ImageView import com.duckduckgo.common.ui.view.DaxSwitch -import com.duckduckgo.common.ui.view.listitem.DaxListItem.LeadingIconSize.Medium +import com.duckduckgo.common.ui.view.listitem.DaxListItem.IconSize.Medium import com.duckduckgo.common.ui.view.text.DaxTextView import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.mobile.android.R @@ -93,13 +93,13 @@ class OneLineListItem @JvmOverloads constructor( ImageBackground.None } - val leadingIconSize = if (hasValue(R.styleable.OneLineListItem_leadingIconSize)) { - LeadingIconSize.from(getInt(R.styleable.OneLineListItem_leadingIconSize, 1)) + val iconSize = if (hasValue(R.styleable.OneLineListItem_leadingIconSize)) { + IconSize.from(getInt(R.styleable.OneLineListItem_leadingIconSize, 1)) } else { Medium } - setLeadingIconSize(leadingIconSize, leadingIconBackground) + setLeadingIconSize(iconSize, leadingIconBackground) val showTrailingIcon = hasValue(R.styleable.OneLineListItem_trailingIcon) val showSwitch = getBoolean(R.styleable.OneLineListItem_showSwitch, false) diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/SettingsListItem.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/SettingsListItem.kt index cd354bba2d9f..a9352c8aa72f 100644 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/SettingsListItem.kt +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/SettingsListItem.kt @@ -19,6 +19,7 @@ package com.duckduckgo.common.ui.view.listitem import android.content.Context import android.util.AttributeSet import android.widget.ImageView +import androidx.annotation.DrawableRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import com.duckduckgo.common.ui.view.StatusIndicatorView @@ -38,13 +39,13 @@ class SettingsListItem @JvmOverloads constructor( private val binding: ViewSettingsListItemBinding by viewBinding() - val primaryText: DaxTextView + private val primaryText: DaxTextView get() = binding.primaryText - val leadingIcon: ImageView + private val leadingIcon: ImageView get() = binding.leadingIcon - val betaPill: ImageView + private val betaPill: ImageView get() = binding.betaPill - val statusIndicator: StatusIndicatorView + private val statusIndicator: StatusIndicatorView get() = binding.statusIndicator init { @@ -65,9 +66,9 @@ class SettingsListItem @JvmOverloads constructor( leadingIcon.gone() } - setPillVisible(getBoolean(R.styleable.SettingsListItem_showBetaPill, false)) + betaPill.isVisible = getBoolean(R.styleable.SettingsListItem_showBetaPill, false) - val indicatorStatus = Status.from(getInt(R.styleable.SettingsListItem_indicatorStatus, 0)) + val indicatorStatus = Status.from(getInt(R.styleable.SettingsListItem_indicatorStatus, 2)) statusIndicator.setStatus(indicatorStatus) recycle() @@ -79,11 +80,13 @@ class SettingsListItem @JvmOverloads constructor( binding.root.setOnClickListener { onClick() } } + /** Sets whether the status indicator is on or off */ fun setStatus(isOn: Boolean) { statusIndicator.setStatus(if (isOn) Status.ON else Status.OFF) } - private fun setPillVisible(isVisible: Boolean) { - betaPill.isVisible = isVisible + /** Sets the leading icon image resource */ + fun setLeadingIconResource(@DrawableRes idRes: Int) { + leadingIcon.setImageResource(idRes) } } diff --git a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/TwoLineListItem.kt b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/TwoLineListItem.kt index da88bf0c09c9..8ca55b8564f6 100644 --- a/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/TwoLineListItem.kt +++ b/common/common-ui/src/main/java/com/duckduckgo/common/ui/view/listitem/TwoLineListItem.kt @@ -23,7 +23,7 @@ import android.util.AttributeSet import android.view.View import android.widget.ImageView import com.duckduckgo.common.ui.view.DaxSwitch -import com.duckduckgo.common.ui.view.listitem.DaxListItem.LeadingIconSize.Medium +import com.duckduckgo.common.ui.view.listitem.DaxListItem.IconSize.Medium import com.duckduckgo.common.ui.view.text.DaxTextView import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.mobile.android.R @@ -100,7 +100,7 @@ class TwoLineListItem @JvmOverloads constructor( } val leadingIconSize = if (hasValue(R.styleable.TwoLineListItem_leadingIconSize)) { - LeadingIconSize.from(getInt(R.styleable.TwoLineListItem_leadingIconSize, 1)) + IconSize.from(getInt(R.styleable.TwoLineListItem_leadingIconSize, 1)) } else { Medium } @@ -114,6 +114,10 @@ class TwoLineListItem @JvmOverloads constructor( setPillVisible(getBoolean(R.styleable.TwoLineListItem_showBetaPill, false)) val showTrailingIcon = hasValue(R.styleable.TwoLineListItem_trailingIcon) + + val trailingIconSize = IconSize.from(getInt(R.styleable.TwoLineListItem_trailingIconSize, Medium.ordinal)) + setTrailingIconSize(trailingIconSize) + val showSwitch = getBoolean(R.styleable.TwoLineListItem_showSwitch, false) when { showSwitch -> showSwitch() diff --git a/common/common-ui/src/main/res/layout/component_two_line_item.xml b/common/common-ui/src/main/res/layout/component_two_line_item.xml index ef9c4dff9d80..6884d1f403fb 100644 --- a/common/common-ui/src/main/res/layout/component_two_line_item.xml +++ b/common/common-ui/src/main/res/layout/component_two_line_item.xml @@ -171,13 +171,37 @@ app:trailingIcon="@drawable/ic_menu_vertical_24" /> + + + + diff --git a/common/common-ui/src/main/res/values/attrs-lists.xml b/common/common-ui/src/main/res/values/attrs-lists.xml index d61b6e549644..d5520e29d512 100644 --- a/common/common-ui/src/main/res/values/attrs-lists.xml +++ b/common/common-ui/src/main/res/values/attrs-lists.xml @@ -54,6 +54,12 @@ + + + + + + @@ -87,6 +93,7 @@ + diff --git a/common/common-ui/src/main/res/drawable/ic_vpn_color_24.xml b/network-protection/network-protection-impl/src/main/res/drawable/ic_vpn_color_24.xml similarity index 81% rename from common/common-ui/src/main/res/drawable/ic_vpn_color_24.xml rename to network-protection/network-protection-impl/src/main/res/drawable/ic_vpn_color_24.xml index 5cc01cc7fc4b..78af318a96f4 100644 --- a/common/common-ui/src/main/res/drawable/ic_vpn_color_24.xml +++ b/network-protection/network-protection-impl/src/main/res/drawable/ic_vpn_color_24.xml @@ -1,3 +1,19 @@ + + + { + binding.subscriptionBuyContainer.gone() + binding.subscriptionRestoreContainer.gone() + binding.subscriptionWaitingContainer.gone() + binding.subscriptionSettingContainer.show() + } + WAITING -> { + binding.subscriptionBuyContainer.gone() + binding.subscriptionWaitingContainer.show() + binding.subscriptionSettingContainer.gone() + binding.subscriptionRestoreContainer.show() + } + EXPIRED, INACTIVE -> { + binding.subscriptionBuy.setPrimaryText(context.getString(R.string.subscriptionSettingExpired)) + binding.subscriptionBuy.setSecondaryText(context.getString(R.string.subscriptionSettingExpiredSubtitle)) + binding.subscriptionBuy.setItemStatus(ALERT) + binding.subscriptionGet.setText(R.string.subscriptionSettingExpiredViewPlans) + binding.subscriptionBuyContainer.show() + binding.subscriptionSettingContainer.show() + binding.subscriptionWaitingContainer.gone() + binding.subscriptionRestoreContainer.gone() + } + else -> { + binding.subscriptionBuy.setPrimaryText(context.getString(R.string.subscriptionSettingSubscribe)) + binding.subscriptionBuy.setSecondaryText( + when (viewState.region) { + ROW -> context.getString(R.string.subscriptionSettingSubscribeSubtitleRow) + US -> context.getString(R.string.subscriptionSettingSubscribeSubtitle) + else -> "" + }, + ) + binding.subscriptionBuy.setItemStatus(DISABLED) + binding.subscriptionGet.setText(R.string.subscriptionSettingGet) + binding.subscriptionBuyContainer.show() + binding.subscriptionSettingContainer.gone() + binding.subscriptionWaitingContainer.gone() + binding.subscriptionRestoreContainer.show() + } + } + } + + private fun processCommands(command: Command) { + when (command) { + is OpenSettings -> { + globalActivityStarter.start(context, SubscriptionsSettingsScreenWithEmptyParams) + } + is OpenBuyScreen -> { + globalActivityStarter.start( + context, + SubscriptionsWebViewActivityWithParams( + url = SubscriptionsConstants.BUY_URL, + ), + ) + } + is OpenRestoreScreen -> { + globalActivityStarter.start(context, RestoreSubscriptionScreenWithParams(isOriginWeb = false)) + } + } + } +} diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyProSettingViewModel.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyProSettingViewModel.kt new file mode 100644 index 000000000000..a824f85d7e50 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyProSettingViewModel.kt @@ -0,0 +1,105 @@ +/* + * 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.SubscriptionStatus +import com.duckduckgo.subscriptions.api.SubscriptionStatus.UNKNOWN +import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.MONTHLY_PLAN_ROW +import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.MONTHLY_PLAN_US +import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender +import com.duckduckgo.subscriptions.impl.settings.views.LegacyProSettingViewModel.Command.OpenBuyScreen +import com.duckduckgo.subscriptions.impl.settings.views.LegacyProSettingViewModel.Command.OpenRestoreScreen +import com.duckduckgo.subscriptions.impl.settings.views.LegacyProSettingViewModel.Command.OpenSettings +import com.duckduckgo.subscriptions.impl.settings.views.LegacyProSettingViewModel.ViewState.SubscriptionRegion +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.distinctUntilChanged +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 LegacyProSettingViewModel @Inject constructor( + private val subscriptionsManager: SubscriptionsManager, + private val pixelSender: SubscriptionPixelSender, +) : ViewModel(), DefaultLifecycleObserver { + + sealed class Command { + data object OpenSettings : Command() + data object OpenBuyScreen : Command() + data object OpenRestoreScreen : Command() + } + + private val command = Channel(1, BufferOverflow.DROP_OLDEST) + internal fun commands(): Flow = command.receiveAsFlow() + data class ViewState( + val status: SubscriptionStatus = UNKNOWN, + val region: SubscriptionRegion? = null, + ) { + enum class SubscriptionRegion { US, ROW } + } + + private val _viewState = MutableStateFlow(ViewState()) + val viewState = _viewState.asStateFlow() + + fun onSettings() { + sendCommand(OpenSettings) + } + + fun onBuy() { + sendCommand(OpenBuyScreen) + } + + fun onRestore() { + pixelSender.reportAppSettingsRestorePurchaseClick() + sendCommand(OpenRestoreScreen) + } + + override fun onCreate(owner: LifecycleOwner) { + super.onCreate(owner) + subscriptionsManager.subscriptionStatus + .distinctUntilChanged() + .onEach { subscriptionStatus -> + val region = when (subscriptionsManager.getSubscriptionOffer()?.monthlyPlanId) { + MONTHLY_PLAN_ROW -> SubscriptionRegion.ROW + MONTHLY_PLAN_US -> SubscriptionRegion.US + else -> null + } + _viewState.emit(viewState.value.copy(status = subscriptionStatus, region = region)) + }.launchIn(viewModelScope) + } + + private fun sendCommand(newCommand: Command) { + viewModelScope.launch { + command.send(newCommand) + } + } +} diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/ProSettingView.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/ProSettingView.kt index 75c91092e91f..4ce3f911ae70 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/ProSettingView.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/views/ProSettingView.kt @@ -20,18 +20,17 @@ 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 import com.duckduckgo.anvil.annotations.InjectWith -import com.duckduckgo.common.ui.view.gone -import com.duckduckgo.common.ui.view.listitem.CheckListItem.CheckItemStatus.ALERT -import com.duckduckgo.common.ui.view.listitem.CheckListItem.CheckItemStatus.DISABLED -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.mobile.android.R as CommonR import com.duckduckgo.navigation.api.GlobalActivityStarter import com.duckduckgo.subscriptions.api.SubscriptionStatus.AUTO_RENEWABLE import com.duckduckgo.subscriptions.api.SubscriptionStatus.EXPIRED @@ -135,42 +134,53 @@ class ProSettingView @JvmOverloads constructor( private fun renderView(viewState: ViewState) { when (viewState.status) { AUTO_RENEWABLE, NOT_AUTO_RENEWABLE, GRACE_PERIOD -> { - binding.subscriptionBuyContainer.gone() - binding.subscriptionRestoreContainer.gone() - binding.subscriptionWaitingContainer.gone() - binding.subscriptionSettingContainer.show() + with(binding) { + subscriptionBuyContainer.isGone = true + subscriptionRestoreContainer.isGone = true + subscriptionSetting.isGone = true + + subscribedSubscriptionSetting.isVisible = true + subscriptionSettingContainer.isVisible = true + } } WAITING -> { - binding.subscriptionBuyContainer.gone() - binding.subscriptionWaitingContainer.show() - binding.subscriptionSettingContainer.gone() - binding.subscriptionRestoreContainer.show() + with(binding) { + subscriptionBuyContainer.isGone = true + subscriptionRestoreContainer.isGone = true + subscribedSubscriptionSetting.isGone = true + + subscriptionSettingContainer.isVisible = true + subscriptionSetting.setSecondaryText(context.getString(R.string.subscriptionSettingActivating)) + } } EXPIRED, INACTIVE -> { - binding.subscriptionBuy.setPrimaryText(context.getString(R.string.subscriptionSettingExpired)) - binding.subscriptionBuy.setSecondaryText(context.getString(R.string.subscriptionSettingExpiredSubtitle)) - binding.subscriptionBuy.setItemStatus(ALERT) - binding.subscriptionGet.setText(R.string.subscriptionSettingExpiredViewPlans) - binding.subscriptionBuyContainer.show() - binding.subscriptionSettingContainer.show() - binding.subscriptionWaitingContainer.gone() - binding.subscriptionRestoreContainer.gone() + with(binding) { + subscriptionBuyContainer.isGone = true + subscriptionRestoreContainer.isGone = true + subscribedSubscriptionSetting.isGone = true + + subscriptionSettingContainer.isVisible = true + subscriptionSetting.setSecondaryText(context.getString(R.string.subscriptionSettingExpired)) + subscriptionSetting.setTrailingIconResource(CommonR.drawable.ic_exclamation_red_16) + } } else -> { - binding.subscriptionBuy.setPrimaryText(context.getString(R.string.subscriptionSettingSubscribe)) - binding.subscriptionBuy.setSecondaryText( - when (viewState.region) { - ROW -> context.getString(R.string.subscriptionSettingSubscribeSubtitleRow) - US -> context.getString(R.string.subscriptionSettingSubscribeSubtitle) - else -> "" - }, - ) - binding.subscriptionBuy.setItemStatus(DISABLED) - binding.subscriptionGet.setText(R.string.subscriptionSettingGet) - binding.subscriptionBuyContainer.show() - binding.subscriptionSettingContainer.gone() - binding.subscriptionWaitingContainer.gone() - binding.subscriptionRestoreContainer.show() + with(binding) { + subscriptionBuy.setPrimaryText(context.getString(R.string.subscriptionSettingSubscribe)) + subscriptionBuy.setSecondaryText( + when (viewState.region) { + ROW -> context.getString(R.string.subscriptionSettingSubscribeSubtitleRow) + US -> context.getString(R.string.subscriptionSettingSubscribeSubtitle) + else -> "" + }, + ) + subscriptionGet.setText(R.string.subscriptionSettingGet) + + subscriptionBuyContainer.isVisible = true + subscriptionRestoreContainer.isVisible = true + + subscriptionSettingContainer.isGone = true + } } } } diff --git a/common/common-ui/src/main/res/drawable/ic_identity_blocked_pir_color_24.xml b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_identity_blocked_pir_color_24.xml similarity index 100% rename from common/common-ui/src/main/res/drawable/ic_identity_blocked_pir_color_24.xml rename to subscriptions/subscriptions-impl/src/main/res/drawable/ic_identity_blocked_pir_color_24.xml diff --git a/common/common-ui/src/main/res/drawable/ic_identity_blocked_pir_grayscale_color_24.xml b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_identity_blocked_pir_grayscale_color_24.xml similarity index 100% rename from common/common-ui/src/main/res/drawable/ic_identity_blocked_pir_grayscale_color_24.xml rename to subscriptions/subscriptions-impl/src/main/res/drawable/ic_identity_blocked_pir_grayscale_color_24.xml diff --git a/common/common-ui/src/main/res/drawable/ic_identity_theft_restoration_color_24.xml b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_identity_theft_restoration_color_24.xml similarity index 100% rename from common/common-ui/src/main/res/drawable/ic_identity_theft_restoration_color_24.xml rename to subscriptions/subscriptions-impl/src/main/res/drawable/ic_identity_theft_restoration_color_24.xml diff --git a/common/common-ui/src/main/res/drawable/ic_identity_theft_restoration_grayscale_color_24.xml b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_identity_theft_restoration_grayscale_color_24.xml similarity index 100% rename from common/common-ui/src/main/res/drawable/ic_identity_theft_restoration_grayscale_color_24.xml rename to subscriptions/subscriptions-impl/src/main/res/drawable/ic_identity_theft_restoration_grayscale_color_24.xml diff --git a/common/common-ui/src/main/res/drawable/ic_privacy_pro_color_24.xml b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_privacy_pro_color_24.xml similarity index 100% rename from common/common-ui/src/main/res/drawable/ic_privacy_pro_color_24.xml rename to subscriptions/subscriptions-impl/src/main/res/drawable/ic_privacy_pro_color_24.xml diff --git a/subscriptions/subscriptions-impl/src/main/res/layout/legacy_view_settings.xml b/subscriptions/subscriptions-impl/src/main/res/layout/legacy_view_settings.xml new file mode 100644 index 000000000000..24b69b96fa8b --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/res/layout/legacy_view_settings.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/subscriptions/subscriptions-impl/src/main/res/layout/view_settings.xml b/subscriptions/subscriptions-impl/src/main/res/layout/view_settings.xml index 24b69b96fa8b..e77bd974cb82 100644 --- a/subscriptions/subscriptions-impl/src/main/res/layout/view_settings.xml +++ b/subscriptions/subscriptions-impl/src/main/res/layout/view_settings.xml @@ -31,10 +31,11 @@ android:visibility="gone" tools:visibility="visible"> - @@ -42,7 +43,7 @@ android:id="@+id/subscriptionGet" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="48dp" + android:layout_marginStart="56dp" android:layout_marginTop="@dimen/keyline_3" android:layout_marginBottom="@dimen/keyline_3" app:textType="primary" @@ -60,33 +61,25 @@ tools:visibility="visible"> - - - - + tools:visibility="visible" + tools:ignore="RtlSymmetry" /> - + diff --git a/subscriptions/subscriptions-impl/src/main/res/values/donottranslate.xml b/subscriptions/subscriptions-impl/src/main/res/values/donottranslate.xml new file mode 100644 index 000000000000..f002ba80c3d9 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/res/values/donottranslate.xml @@ -0,0 +1,22 @@ + + + + + + Activating + + diff --git a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyLegacyProSettingViewModelTest.kt b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyLegacyProSettingViewModelTest.kt new file mode 100644 index 000000000000..f1587dca900b --- /dev/null +++ b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/settings/views/LegacyLegacyProSettingViewModelTest.kt @@ -0,0 +1,82 @@ +package com.duckduckgo.subscriptions.impl.settings.views + +import app.cash.turbine.test +import com.duckduckgo.common.test.CoroutineTestRule +import com.duckduckgo.subscriptions.api.SubscriptionStatus +import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender +import com.duckduckgo.subscriptions.impl.settings.views.LegacyProSettingViewModel.Command.OpenBuyScreen +import com.duckduckgo.subscriptions.impl.settings.views.LegacyProSettingViewModel.Command.OpenRestoreScreen +import com.duckduckgo.subscriptions.impl.settings.views.LegacyProSettingViewModel.Command.OpenSettings +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever + +class LegacyLegacyProSettingViewModelTest { + @get:Rule + val coroutineTestRule: CoroutineTestRule = CoroutineTestRule() + + private val subscriptionsManager: SubscriptionsManager = mock() + private val pixelSender: SubscriptionPixelSender = mock() + private lateinit var viewModel: LegacyProSettingViewModel + + @Before + fun before() { + viewModel = LegacyProSettingViewModel(subscriptionsManager, pixelSender) + } + + @Test + fun whenOnSettingsThenCommandSent() = runTest { + viewModel.commands().test { + viewModel.onSettings() + assertTrue(awaitItem() is OpenSettings) + cancelAndConsumeRemainingEvents() + } + } + + @Test + fun whenOnBuyThenCommandSent() = runTest { + viewModel.commands().test { + viewModel.onBuy() + assertTrue(awaitItem() is OpenBuyScreen) + cancelAndConsumeRemainingEvents() + } + } + + @Test + fun whenOnRestoreThenCommandSent() = runTest { + viewModel.commands().test { + viewModel.onRestore() + assertTrue(awaitItem() is OpenRestoreScreen) + cancelAndConsumeRemainingEvents() + } + } + + @Test + fun whenOnResumeEmitViewState() = runTest { + whenever(subscriptionsManager.subscriptionStatus).thenReturn(flowOf(SubscriptionStatus.EXPIRED)) + + viewModel.onCreate(mock()) + viewModel.viewState.test { + assertEquals(SubscriptionStatus.EXPIRED, awaitItem().status) + cancelAndConsumeRemainingEvents() + } + } + + @Test + fun whenOnRestoreThenPixelSent() = runTest { + viewModel.commands().test { + viewModel.onRestore() + verify(pixelSender).reportAppSettingsRestorePurchaseClick() + verifyNoMoreInteractions(pixelSender) + cancelAndConsumeRemainingEvents() + } + } +}