From fd8877ead2a5295f1cdc3be46d2190612731d91f Mon Sep 17 00:00:00 2001 From: Lukasz Macionczyk Date: Thu, 1 Aug 2024 23:03:55 +0200 Subject: [PATCH] Updates to subscription settings UI (#4809) Task/Issue URL: https://app.asana.com/0/1205648422731273/1207910266159868/f ### Description This PR updates subscription settings UI to make them more consistent across platforms. ### Steps to test this PR See task. ### UI changes | Before | After | | ------ | ----- | |![app-settings-unsubscribed-old](https://github.com/user-attachments/assets/f0847315-ec35-4c60-965a-b52c6202ac36)|![app-settings-unsubscribed-new](https://github.com/user-attachments/assets/0e3a68d9-676c-4aec-b071-ff462b48ec0a)| |![app-settings-subscribed-old](https://github.com/user-attachments/assets/64467616-fe4c-4980-b986-659379ac45e1)|![app-settings-subscribed-new](https://github.com/user-attachments/assets/fa8acb54-7383-457d-8202-ac76afed717f)| |![subs-settings-subscribed-old](https://github.com/user-attachments/assets/6af2d28d-d453-4fe4-b333-617de6d33d2d)|![subs-settings-subscribed-new](https://github.com/user-attachments/assets/d0787ebc-fc37-4dec-9ba8-caf9e69d2772)| --- .../settings/ProSettingNetPView.kt | 8 +- .../settings/ProSettingNetPViewModel.kt | 14 ++-- .../res/drawable/ic_check_grey_round_16.xml | 18 ----- .../main/res/layout/view_settings_netp.xml | 8 +- .../src/main/res/values/donottranslate.xml | 1 - .../settings/ProSettingNetPViewModelTest.kt | 11 ++- .../impl/settings/views/ProSettingView.kt | 13 ---- .../impl/ui/SubscriptionSettingsActivity.kt | 26 ++++++- .../src/main/res/drawable/ic_dot_green.xml | 26 +++++++ .../drawable/ic_privacy_pro_settings_hero.xml | 76 +++++++++++++++++++ .../layout/activity_subscription_settings.xml | 49 ++++++++++-- .../src/main/res/layout/view_itr_settings.xml | 6 +- .../src/main/res/layout/view_pir_settings.xml | 6 +- .../src/main/res/layout/view_settings.xml | 9 +-- .../src/main/res/values/donottranslate.xml | 11 ++- 15 files changed, 196 insertions(+), 86 deletions(-) delete mode 100644 network-protection/network-protection-impl/src/main/res/drawable/ic_check_grey_round_16.xml create mode 100644 subscriptions/subscriptions-impl/src/main/res/drawable/ic_dot_green.xml create mode 100644 subscriptions/subscriptions-impl/src/main/res/drawable/ic_privacy_pro_settings_hero.xml diff --git a/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPView.kt b/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPView.kt index ed6c5658f713..c82423994b22 100644 --- a/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPView.kt +++ b/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPView.kt @@ -28,8 +28,8 @@ import com.duckduckgo.common.ui.view.gone import com.duckduckgo.common.ui.view.show import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.di.scopes.ViewScope +import com.duckduckgo.mobile.android.R as CommonR import com.duckduckgo.navigation.api.GlobalActivityStarter -import com.duckduckgo.networkprotection.impl.R import com.duckduckgo.networkprotection.impl.databinding.ViewSettingsNetpBinding import com.duckduckgo.networkprotection.impl.subscription.settings.ProSettingNetPViewModel.Command import com.duckduckgo.networkprotection.impl.subscription.settings.ProSettingNetPViewModel.Command.OpenNetPScreen @@ -96,13 +96,11 @@ class ProSettingNetPView @JvmOverloads constructor( Hidden -> this.gone() Pending -> { this.show() - this.setSecondaryText(context.getString(R.string.netpSubscriptionSettingsNeverEnabled)) - this.setItemStatus(com.duckduckgo.common.ui.view.listitem.CheckListItem.CheckItemStatus.DISABLED) + this.setLeadingIconResource(CommonR.drawable.ic_check_grey_round_16) } is ShowState -> { this.show() - this.setSecondaryText(context.getString(networkProtectionEntryState.subtitle)) - this.setItemStatus(networkProtectionEntryState.icon) + this.setLeadingIconResource(networkProtectionEntryState.icon) } } } diff --git a/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPViewModel.kt b/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPViewModel.kt index 441a8421fd9b..866d8ca0887f 100644 --- a/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPViewModel.kt +++ b/network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPViewModel.kt @@ -17,6 +17,7 @@ package com.duckduckgo.networkprotection.impl.subscription.settings import android.annotation.SuppressLint +import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner @@ -24,9 +25,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.duckduckgo.app.statistics.pixels.Pixel -import com.duckduckgo.common.ui.view.listitem.CheckListItem -import com.duckduckgo.common.ui.view.listitem.CheckListItem.CheckItemStatus import com.duckduckgo.common.utils.DispatcherProvider +import com.duckduckgo.mobile.android.R as CommonR import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams import com.duckduckgo.networkprotection.api.NetworkProtectionAccessState import com.duckduckgo.networkprotection.api.NetworkProtectionAccessState.NetPAccessState @@ -72,7 +72,7 @@ class ProSettingNetPViewModel( data object Hidden : NetPEntryState() data object Pending : NetPEntryState() data class ShowState( - val icon: CheckItemStatus, + @DrawableRes val icon: Int, @StringRes val subtitle: Int, ) : NetPEntryState() } @@ -119,14 +119,14 @@ class ProSettingNetPViewModel( else -> R.string.netpSubscriptionSettingsDisconnected } - val netPItemStatus = if (networkProtectionConnectionState != DISCONNECTED) { - CheckListItem.CheckItemStatus.ENABLED + val netPItemIcon = if (networkProtectionConnectionState != DISCONNECTED) { + CommonR.drawable.ic_check_green_round_16 } else { - CheckListItem.CheckItemStatus.WARNING + CommonR.drawable.ic_exclamation_yellow_16 } ShowState( - icon = netPItemStatus, + icon = netPItemIcon, subtitle = subtitle, ) } else { diff --git a/network-protection/network-protection-impl/src/main/res/drawable/ic_check_grey_round_16.xml b/network-protection/network-protection-impl/src/main/res/drawable/ic_check_grey_round_16.xml deleted file mode 100644 index 2b1923a65359..000000000000 --- a/network-protection/network-protection-impl/src/main/res/drawable/ic_check_grey_round_16.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - diff --git a/network-protection/network-protection-impl/src/main/res/layout/view_settings_netp.xml b/network-protection/network-protection-impl/src/main/res/layout/view_settings_netp.xml index 48da165cc7b1..6fbef946751f 100644 --- a/network-protection/network-protection-impl/src/main/res/layout/view_settings_netp.xml +++ b/network-protection/network-protection-impl/src/main/res/layout/view_settings_netp.xml @@ -14,12 +14,12 @@ ~ limitations under the License. --> - \ No newline at end of file +app:leadingIconSize="small" +app:leadingIcon="@drawable/ic_check_grey_round_16" +app:primaryText="@string/netpSubscriptionSettingsTitle" /> \ No newline at end of file diff --git a/network-protection/network-protection-impl/src/main/res/values/donottranslate.xml b/network-protection/network-protection-impl/src/main/res/values/donottranslate.xml index 56ff16ec10dd..a948092b9c0d 100644 --- a/network-protection/network-protection-impl/src/main/res/values/donottranslate.xml +++ b/network-protection/network-protection-impl/src/main/res/values/donottranslate.xml @@ -210,7 +210,6 @@ Enabled Connecting… Disabled - Secure your network connection anytime, anywhere DNS diff --git a/network-protection/network-protection-impl/src/test/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPViewModelTest.kt b/network-protection/network-protection-impl/src/test/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPViewModelTest.kt index 49e41b8e06a4..f46553aad3a8 100644 --- a/network-protection/network-protection-impl/src/test/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPViewModelTest.kt +++ b/network-protection/network-protection-impl/src/test/java/com/duckduckgo/networkprotection/impl/subscription/settings/ProSettingNetPViewModelTest.kt @@ -19,8 +19,7 @@ package com.duckduckgo.networkprotection.impl.subscription.settings import app.cash.turbine.test import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.common.test.CoroutineTestRule -import com.duckduckgo.common.ui.view.listitem.CheckListItem.CheckItemStatus.ENABLED -import com.duckduckgo.common.ui.view.listitem.CheckListItem.CheckItemStatus.WARNING +import com.duckduckgo.mobile.android.R as CommonR import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams import com.duckduckgo.networkprotection.api.NetworkProtectionAccessState import com.duckduckgo.networkprotection.api.NetworkProtectionAccessState.NetPAccessState.Locked @@ -122,7 +121,7 @@ class ProSettingNetPViewModelTest { proSettingNetPViewModel.viewState.test { assertEquals( ShowState( - icon = ENABLED, + icon = CommonR.drawable.ic_check_green_round_16, subtitle = R.string.netpSubscriptionSettingsConnected, ), expectMostRecentItem().networkProtectionEntryState, @@ -157,7 +156,7 @@ class ProSettingNetPViewModelTest { proSettingNetPViewModel.viewState.test { assertEquals( ShowState( - icon = ENABLED, + icon = CommonR.drawable.ic_check_green_round_16, subtitle = R.string.netpSubscriptionSettingsConnected, ), expectMostRecentItem().networkProtectionEntryState, @@ -176,7 +175,7 @@ class ProSettingNetPViewModelTest { proSettingNetPViewModel.viewState.test { assertEquals( ShowState( - icon = ENABLED, + icon = CommonR.drawable.ic_check_green_round_16, subtitle = R.string.netpSubscriptionSettingsConnecting, ), expectMostRecentItem().networkProtectionEntryState, @@ -195,7 +194,7 @@ class ProSettingNetPViewModelTest { proSettingNetPViewModel.viewState.test { assertEquals( ShowState( - icon = WARNING, + icon = CommonR.drawable.ic_exclamation_yellow_16, subtitle = R.string.netpSubscriptionSettingsDisconnected, ), expectMostRecentItem().networkProtectionEntryState, 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 caed16502b51..6cb0f2fb7f12 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 @@ -33,7 +33,6 @@ 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.common.utils.extensions.html import com.duckduckgo.di.scopes.ViewScope import com.duckduckgo.navigation.api.GlobalActivityStarter import com.duckduckgo.subscriptions.api.SubscriptionStatus.AUTO_RENEWABLE @@ -152,7 +151,6 @@ class ProSettingView @JvmOverloads constructor( binding.subscriptionBuy.setSecondaryText(context.getString(R.string.subscriptionSettingExpiredSubtitle)) binding.subscriptionBuy.setItemStatus(ALERT) binding.subscriptionGet.setText(R.string.subscriptionSettingExpiredViewPlans) - binding.subscribeSecondary.gone() binding.subscriptionBuyContainer.show() binding.subscriptionSettingContainer.show() binding.subscriptionWaitingContainer.gone() @@ -163,9 +161,6 @@ class ProSettingView @JvmOverloads constructor( binding.subscriptionBuy.setSecondaryText(context.getString(R.string.subscriptionSettingSubscribeSubtitle)) binding.subscriptionBuy.setItemStatus(DISABLED) binding.subscriptionGet.setText(R.string.subscriptionSettingGet) - val htmlText = context.getString(R.string.subscriptionSettingFeaturesList).html(context) - binding.subscribeSecondary.text = htmlText.noTrailingWhiteLines() - binding.subscribeSecondary.show() binding.subscriptionBuyContainer.show() binding.subscriptionSettingContainer.gone() binding.subscriptionWaitingContainer.gone() @@ -203,11 +198,3 @@ class SubscriptionSettingLayout @JvmOverloads constructor( return true } } - -private fun CharSequence.noTrailingWhiteLines(): CharSequence { - var text = this - while (text[text.length - 1] == '\n') { - text = text.subSequence(0, text.length - 1) - } - return text -} diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionSettingsActivity.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionSettingsActivity.kt index f78d0b2e884f..b2fbd967fd67 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionSettingsActivity.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionSettingsActivity.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import com.duckduckgo.anvil.annotations.ContributeToActivityStarter import com.duckduckgo.anvil.annotations.InjectWith +import com.duckduckgo.browser.api.ui.BrowserScreens.WebViewActivityWithParams import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder import com.duckduckgo.common.ui.viewbinding.viewBinding @@ -34,7 +35,7 @@ import com.duckduckgo.navigation.api.GlobalActivityStarter import com.duckduckgo.subscriptions.api.SubscriptionStatus.AUTO_RENEWABLE import com.duckduckgo.subscriptions.api.SubscriptionStatus.EXPIRED import com.duckduckgo.subscriptions.api.SubscriptionStatus.INACTIVE -import com.duckduckgo.subscriptions.impl.R.string +import com.duckduckgo.subscriptions.impl.R.* import com.duckduckgo.subscriptions.impl.SubscriptionsConstants import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.BASIC_SUBSCRIPTION import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.FAQS_URL @@ -126,6 +127,10 @@ class SubscriptionSettingsActivity : DuckDuckGoActivity() { goToPurchasePage() } + binding.privacyPolicy.setOnClickListener { + goToPrivacyPolicy() + } + if (savedInstanceState == null) { pixelSender.reportSubscriptionSettingsShown() } @@ -140,12 +145,14 @@ class SubscriptionSettingsActivity : DuckDuckGoActivity() { if (viewState.status in listOf(INACTIVE, EXPIRED)) { binding.viewPlans.isVisible = true binding.changePlan.isVisible = false - binding.expiredWarningContainer.isVisible = true - binding.description.text = getString(string.subscriptionsExpiredData, viewState.date) + binding.subscriptionActiveStatusContainer.isVisible = false + binding.subscriptionExpiredStatusContainer.isVisible = true + binding.subscriptionExpiredStatusText.text = getString(string.subscriptionsExpiredData, viewState.date) } else { binding.viewPlans.isVisible = false binding.changePlan.isVisible = true - binding.expiredWarningContainer.isVisible = false + binding.subscriptionActiveStatusContainer.isVisible = true + binding.subscriptionExpiredStatusContainer.isVisible = false val status = when (viewState.status) { AUTO_RENEWABLE -> getString(string.renews) @@ -265,11 +272,22 @@ class SubscriptionSettingsActivity : DuckDuckGoActivity() { ) } + private fun goToPrivacyPolicy() { + globalActivityStarter.start( + this, + WebViewActivityWithParams( + url = PRIVACY_POLICY_URL, + screenTitle = getString(string.privacyPolicyAndTermsOfService), + ), + ) + } + companion object { const val URL = "https://play.google.com/store/account/subscriptions?sku=%s&package=%s" const val ADD_EMAIL_URL = "https://duckduckgo.com/subscriptions/add-email" const val MANAGE_URL = "https://duckduckgo.com/subscriptions/manage" const val LEARN_MORE_URL = "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/adding-email" + const val PRIVACY_POLICY_URL = "https://duckduckgo.com/pro/privacy-terms" data object SubscriptionsSettingsScreenWithEmptyParams : GlobalActivityStarter.ActivityParams } } diff --git a/subscriptions/subscriptions-impl/src/main/res/drawable/ic_dot_green.xml b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_dot_green.xml new file mode 100644 index 000000000000..f83953197234 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_dot_green.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/subscriptions/subscriptions-impl/src/main/res/drawable/ic_privacy_pro_settings_hero.xml b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_privacy_pro_settings_hero.xml new file mode 100644 index 000000000000..643948548002 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/res/drawable/ic_privacy_pro_settings_hero.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + diff --git a/subscriptions/subscriptions-impl/src/main/res/layout/activity_subscription_settings.xml b/subscriptions/subscriptions-impl/src/main/res/layout/activity_subscription_settings.xml index d82a75dcc949..47019ed9490c 100644 --- a/subscriptions/subscriptions-impl/src/main/res/layout/activity_subscription_settings.xml +++ b/subscriptions/subscriptions-impl/src/main/res/layout/activity_subscription_settings.xml @@ -42,27 +42,27 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="@dimen/keyline_4" - android:src="@drawable/ic_privacy_pro"/> + android:src="@drawable/ic_privacy_pro_settings_hero"/> + android:gravity="center" + android:visibility="gone"> + + + + + + + + + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/keyline_4" /> + + diff --git a/subscriptions/subscriptions-impl/src/main/res/layout/view_itr_settings.xml b/subscriptions/subscriptions-impl/src/main/res/layout/view_itr_settings.xml index 4c399028bcf8..bc64f3a43aa1 100644 --- a/subscriptions/subscriptions-impl/src/main/res/layout/view_itr_settings.xml +++ b/subscriptions/subscriptions-impl/src/main/res/layout/view_itr_settings.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> - \ No newline at end of file + app:leadingIconSize="small" + app:leadingIcon="@drawable/ic_check_green_round_16" /> \ No newline at end of file diff --git a/subscriptions/subscriptions-impl/src/main/res/layout/view_pir_settings.xml b/subscriptions/subscriptions-impl/src/main/res/layout/view_pir_settings.xml index 1e14d6b6ebd0..93a12f1a12d0 100644 --- a/subscriptions/subscriptions-impl/src/main/res/layout/view_pir_settings.xml +++ b/subscriptions/subscriptions-impl/src/main/res/layout/view_pir_settings.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> - \ No newline at end of file + app:leadingIconSize="small" + app:leadingIcon="@drawable/ic_check_green_round_16" /> \ 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 d39ce9940519..71e61854771f 100644 --- a/subscriptions/subscriptions-impl/src/main/res/layout/view_settings.xml +++ b/subscriptions/subscriptions-impl/src/main/res/layout/view_settings.xml @@ -36,16 +36,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:primaryText="@string/subscriptionSettingSubscribe" + app:primaryTextTruncated="false" app:secondaryText="@string/subscriptionSettingSubscribeSubtitle" /> - - expires Activate on Other Devices Add Email - Add an optional email to your subscription or use your Google Account to access Privacy Pro on other devices. + Add an optional email to your subscription to access Privacy\u00A0Pro on other devices. Edit Email Use this email to activate your subscription in Settings > Privacy Pro in the DuckDuckGo app on your other devices. Learn More Need help with Privacy Pro? Get answers to frequently asked questions or contact Privacy Pro support from our help pages. + Subscribed + Privacy Policy and Terms of Service Subscription Settings - Subscribe To Privacy Pro - More seamless privacy with three new protections, including: -
  •  VPN
  •  Personal Information Removal
  •  Identity Theft Restoration
  • ]]>
    + Protect your connection and identity with Privacy Pro + Includes our VPN, Personal Information Removal, and Identity Theft Restoration. Get Privacy Pro I Have a Subscription Your subscription is being activated @@ -84,11 +85,9 @@ Personal Information Removal - Remove your info from sites that sell it Identity Theft Restoration - If your identity is stolen, we\'ll help restore it Update Plan or Cancel