Skip to content

Commit 0184484

Browse files
authored
Privacy Pro Rebranding (#6464)
Task/Issue URL: https://app.asana.com/1/137249556945/task/1210832122593774?focus=true ### Description Implement all copy changes for Privacy Pro rebranding under a feature flag ### Steps to test this PR _Pre steps_ - [ ] Apply patch attached on https://app.asana.com/1/137249556945/project/1209991789468715/task/1210448620621729?focus=true - [ ] Open https://app.asana.com/1/137249556945/project/1201632632419011/task/1210789040363833?focus=true and https://app.asana.com/1/137249556945/task/1210879129186147?focus=true for checking copy updates _Onboarding_ - [ ] Fresh install - [ ] Go through onboarding and after End Dialog, make sure DuckDuckGo subscription dialog have the updated copy _App Settings screen_ - [ ] Go to App Settings - [ ] Check App Settings Privacy Pro text is updated _App Settings > About_ - [ ] Go to About option - [ ] Check content is updated _AppTP_ - [ ] Go to Settings > App Tracking Protection - [ ] Go through AppTP onboarding - [ ] Check message CTA description is updated _I Have a Subscription_ - [ ] Go to Settings > I Have a Subscription - [ ] Check content is updated _Subscription WebView Page_ - [ ] Go to Settings > Privacy Pro - [ ] Check toolbar title is updated with 'DuckDuckGo Subscription' _Subscription Settings_ - [ ] Purchase a plan - [ ] Go to App Settings > Subscription Settings - [ ] Check content is updated _VPN_ - [ ] Enable VPN - [ ] Cancel Subscription - [ ] Wait until subscription is expired - If VPN disconnected alert doesn't shows up, do the following: - [ ] Close and open app again - [ ] Go to Settings - [ ] Check VPN disconnected dialog appears and content is updated _Expired Subscription_ - [ ] Go to App Settings - [ ] Check You see updated content for Subscription Settings secondary text - [ ] Go to Subscription Settings - [ ] Check content is updated _FF Disabled (Optional)_ - [ ] Fresh install - [ ] Go to Settings > Feature Flag Inventory - [ ] Disable privacyPro > subscriptionRebranding FF - [ ] Check all previous rebranding updates are not showing ### UI changes Check https://app.asana.com/1/137249556945/task/1210879129186147?focus=true
1 parent d73035d commit 0184484

File tree

24 files changed

+211
-69
lines changed

24 files changed

+211
-69
lines changed

app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/ui/tracker_activity/view/message/PProUpsellBannerPlugin.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,12 @@ class PProUpsellBannerPlugin @Inject constructor(
5656
): View? {
5757
val isEligible = runBlocking { subscriptions.isUpsellEligible() && !vpnStore.isPproUpsellBannerDismised() }
5858
return if (isEligible) {
59-
val subtitle: String
6059
val actionText: String
6160
runBlocking {
62-
if (subscriptions.isFreeTrialEligible() && appTPStateMessageToggle.freeTrialCopy().isEnabled()) {
63-
subtitle = context.getString(R.string.apptp_PproUpsellBannerMessage_freeTrial)
64-
actionText = context.getString(R.string.apptp_PproUpsellBannerAction_freeTrial)
61+
actionText = if (subscriptions.isFreeTrialEligible() && appTPStateMessageToggle.freeTrialCopy().isEnabled()) {
62+
context.getString(R.string.apptp_PproUpsellBannerAction_freeTrial)
6563
} else {
66-
subtitle = context.getString(R.string.apptp_PproUpsellBannerMessage)
67-
actionText = context.getString(R.string.apptp_PproUpsellBannerAction)
64+
context.getString(R.string.apptp_PproUpsellBannerAction)
6865
}
6966
}
7067
MessageCta(context)
@@ -73,7 +70,7 @@ class PProUpsellBannerPlugin @Inject constructor(
7370
Message(
7471
topIllustration = com.duckduckgo.mobile.android.R.drawable.ic_privacy_pro,
7572
title = context.getString(R.string.apptp_PproUpsellBannerTitle),
76-
subtitle = subtitle,
73+
subtitle = context.getString(R.string.apptp_PproUpsellBannerMessage_freeTrial),
7774
action = actionText,
7875
messageType = REMOTE_MESSAGE,
7976
),

app/src/main/java/com/duckduckgo/app/about/AboutDuckDuckGoActivity.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import com.duckduckgo.app.about.AboutDuckDuckGoViewModel.Command.LaunchWebViewWi
4040
import com.duckduckgo.app.about.AboutDuckDuckGoViewModel.Command.LaunchWebViewWithPPROUrl
4141
import com.duckduckgo.app.about.AboutDuckDuckGoViewModel.Command.LaunchWebViewWithPrivacyPolicyUrl
4242
import com.duckduckgo.app.about.AboutDuckDuckGoViewModel.Command.LaunchWebViewWithVPNUrl
43+
import com.duckduckgo.app.about.AboutDuckDuckGoViewModel.ViewState
4344
import com.duckduckgo.app.browser.BrowserActivity
4445
import com.duckduckgo.app.browser.R
4546
import com.duckduckgo.app.browser.databinding.ActivityAboutDuckDuckGoBinding
@@ -86,23 +87,26 @@ class AboutDuckDuckGoActivity : DuckDuckGoActivity() {
8687

8788
configureUiEventHandlers()
8889
observeViewModel()
89-
configureClickableLinks()
9090
}
9191

9292
override fun onResume() {
9393
super.onResume()
9494
viewModel.resetEasterEggCounter()
9595
}
9696

97-
private fun configureClickableLinks() {
97+
private fun configureClickableLinks(viewState: ViewState) {
9898
with(binding.includeContent.aboutText) {
99-
text = addClickableLinks()
99+
text = addClickableLinks(viewState)
100100
movementMethod = LinkMovementMethod.getInstance()
101101
}
102102
}
103103

104-
private fun addClickableLinks(): SpannableString {
105-
val fullText = getText(R.string.aboutDescriptionBrandUpdate2025) as SpannedString
104+
private fun addClickableLinks(viewState: ViewState): SpannableString {
105+
val fullText = if (viewState.rebrandingEnabled) {
106+
getText(R.string.aboutDescriptionBrandUpdate2025Rebranding) as SpannedString
107+
} else {
108+
getText(R.string.aboutDescriptionBrandUpdate2025) as SpannedString
109+
}
106110

107111
val spannableString = SpannableString(fullText)
108112
val annotations = fullText.getSpans(0, fullText.length, Annotation::class.java)
@@ -190,6 +194,7 @@ class AboutDuckDuckGoActivity : DuckDuckGoActivity() {
190194
.onEach { viewState ->
191195
viewState.let {
192196
binding.includeContent.aboutVersion.setSecondaryText(it.version)
197+
configureClickableLinks(viewState)
193198
}
194199
}.launchIn(lifecycleScope)
195200

app/src/main/java/com/duckduckgo/app/about/AboutDuckDuckGoViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.duckduckgo.appbuildconfig.api.AppBuildConfig
2626
import com.duckduckgo.di.scopes.ActivityScope
2727
import com.duckduckgo.subscriptions.api.PrivacyProUnifiedFeedback
2828
import com.duckduckgo.subscriptions.api.PrivacyProUnifiedFeedback.PrivacyProFeedbackSource.DDG_SETTINGS
29+
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
2930
import javax.inject.Inject
3031
import kotlinx.coroutines.channels.BufferOverflow
3132
import kotlinx.coroutines.channels.Channel
@@ -42,10 +43,12 @@ class AboutDuckDuckGoViewModel @Inject constructor(
4243
private val appBuildConfig: AppBuildConfig,
4344
private val pixel: Pixel,
4445
private val privacyProUnifiedFeedback: PrivacyProUnifiedFeedback,
46+
private val subscriptionRebrandingFeatureToggle: SubscriptionRebrandingFeatureToggle,
4547
) : ViewModel() {
4648

4749
data class ViewState(
4850
val version: String = "",
51+
val rebrandingEnabled: Boolean = false,
4952
)
5053

5154
sealed class Command {
@@ -69,6 +72,7 @@ class AboutDuckDuckGoViewModel @Inject constructor(
6972
viewState.emit(
7073
currentViewState().copy(
7174
version = obtainVersion(),
75+
rebrandingEnabled = subscriptionRebrandingFeatureToggle.isSubscriptionRebrandingEnabled(),
7276
),
7377
)
7478
}

app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,11 +262,7 @@ class CtaViewModel @Inject constructor(
262262
// Privacy Pro
263263
canShowPrivacyProCta() && !onboardingDesignExperimentToggles.buckOnboarding().isEnabled() -> {
264264
val titleRes: Int = R.string.onboardingPrivacyProDaxDialogTitle
265-
val descriptionRes: Int = if (freeTrialCopyAvailable()) {
266-
R.string.onboardingPrivacyProDaxDialogFreeTrialDescription
267-
} else {
268-
R.string.onboardingPrivacyProDaxDialogDescription
269-
}
265+
val descriptionRes: Int = R.string.onboardingPrivacyProDaxDialogDescriptionRebranding
270266
val primaryCtaRes: Int = if (freeTrialCopyAvailable()) {
271267
R.string.onboardingPrivacyProDaxDialogFreeTrialOkButton
272268
} else {

app/src/main/res/values/donottranslate.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,9 @@
6565

6666
<!-- Section in main app settings -->
6767
<string name="settingsHeadingCompleteSetup">Complete Your Setup</string>
68+
69+
<!-- Privacy Pro Rebranding-->
70+
<string name="aboutDescriptionBrandUpdate2025Rebranding">Welcome to the Duck Side!\n\nDuckDuckGo is the independent online protection company for anyone who wants to take back control of their personal information.\n\nWe believe the best way to protect your personal information from hackers, scammers, and privacy-invasive companies is to stop it from being collected at all. That\'s why millions of people <annotation type="chart_comparison">choose DuckDuckGo over Chrome and other browsers</annotation> to search and browse online. Our built-in search engine is like Google but never tracks your searches. And our browsing protections, such as ad tracker blocking and cookie blocking, help stop other companies from collecting your data. Oh, and our browser is free — we make money from <annotation type="privacy_respecting_ads">privacy-respecting search ads</annotation>, not by exploiting your data.\n\nIn addition, we also offer <annotation type="ppro_help_page">DuckDuckGo subscription</annotation> integrated into our browser. Subscribers get even more privacy protection with a fast and simple <annotation type="vpn_help_page">VPN</annotation> that helps secure your online activity, access to advanced AI models in our private AI chat service Duck.ai, a service that finds and removes your personal information from people-finder sites, and a service that restores your identity should it be stolen.\n\nTake back control of your personal information with the browser designed for data protection, not data collection.</string>
71+
<string name="onboardingPrivacyProDaxDialogDescriptionRebranding"><![CDATA[DuckDuckGo also has an <b>optional paid subscription</b>, with a secure <b>VPN</b> and advanced, private AI.]]></string>
72+
6873
</resources>

app/src/test/java/com/duckduckgo/app/about/AboutDuckDuckGoViewModelTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel
2525
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
2626
import com.duckduckgo.common.test.CoroutineTestRule
2727
import com.duckduckgo.subscriptions.api.PrivacyProUnifiedFeedback
28+
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
2829
import kotlinx.coroutines.test.runTest
2930
import org.junit.Assert.assertEquals
3031
import org.junit.Assert.assertFalse
@@ -58,6 +59,9 @@ internal class AboutDuckDuckGoViewModelTest {
5859
@Mock
5960
private lateinit var privacyProUnifiedFeedback: PrivacyProUnifiedFeedback
6061

62+
@Mock
63+
private lateinit var mockSubscriptionRebrandingFeatureToggle: SubscriptionRebrandingFeatureToggle
64+
6165
@Before
6266
fun before() {
6367
MockitoAnnotations.openMocks(this)
@@ -69,6 +73,7 @@ internal class AboutDuckDuckGoViewModelTest {
6973
mockAppBuildConfig,
7074
mockPixel,
7175
privacyProUnifiedFeedback,
76+
mockSubscriptionRebrandingFeatureToggle,
7277
)
7378
}
7479

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/notification/NetPDisabledNotificationBuilder.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import com.duckduckgo.networkprotection.impl.R
3232
import com.duckduckgo.networkprotection.impl.subscription.NetpSubscriptionManager
3333
import com.duckduckgo.networkprotection.impl.subscription.isActive
3434
import com.duckduckgo.networkprotection.impl.subscription.isExpired
35+
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
3536
import com.squareup.anvil.annotations.ContributesBinding
3637
import java.text.DateFormat
3738
import java.text.SimpleDateFormat
@@ -58,6 +59,7 @@ class RealNetPDisabledNotificationBuilder @Inject constructor(
5859
private val netPNotificationActions: NetPNotificationActions,
5960
private val globalActivityStarter: GlobalActivityStarter,
6061
private val netpSubscriptionManager: NetpSubscriptionManager,
62+
private val subscriptionRebrandingFeatureToggle: SubscriptionRebrandingFeatureToggle,
6163
) : NetPDisabledNotificationBuilder {
6264
private val defaultDateTimeFormatter = SimpleDateFormat.getTimeInstance(DateFormat.SHORT)
6365

@@ -183,14 +185,19 @@ class RealNetPDisabledNotificationBuilder @Inject constructor(
183185
addNextIntentWithParentStack(intent)
184186
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
185187
}
188+
val notificationDescriptionRes = if (subscriptionRebrandingFeatureToggle.isSubscriptionRebrandingEnabled()) {
189+
R.string.netpNotificationVpnAccessRevokedRebranding
190+
} else {
191+
R.string.netpNotificationVpnAccessRevoked
192+
}
186193

187194
return NotificationCompat.Builder(context, NETP_ALERTS_CHANNEL_ID)
188195
.setSmallIcon(com.duckduckgo.mobile.android.R.drawable.notification_logo)
189196
.setContentIntent(pendingIntent)
190-
.setContentText(context.getString(R.string.netpNotificationVpnAccessRevoked))
197+
.setContentText(context.getString(notificationDescriptionRes))
191198
.setStyle(
192199
NotificationCompat.BigTextStyle().bigText(
193-
context.getString(R.string.netpNotificationVpnAccessRevoked),
200+
context.getString(notificationDescriptionRes),
194201
),
195202
)
196203
.setPriority(NotificationCompat.PRIORITY_DEFAULT)

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/revoked/AccessRevokedDialog.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.duckduckgo.di.scopes.AppScope
2828
import com.duckduckgo.navigation.api.GlobalActivityStarter
2929
import com.duckduckgo.networkprotection.impl.R
3030
import com.duckduckgo.networkprotection.impl.pixels.NetworkProtectionPixels
31+
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
3132
import com.duckduckgo.subscriptions.api.SubscriptionScreens.SubscriptionPurchase
3233
import com.squareup.anvil.annotations.ContributesBinding
3334
import javax.inject.Inject
@@ -60,6 +61,7 @@ class RealAccessRevokedDialog @Inject constructor(
6061
private val globalActivityStarter: GlobalActivityStarter,
6162
private val networkProtectionPixels: NetworkProtectionPixels,
6263
private val sharedPreferencesProvider: SharedPreferencesProvider,
64+
private val subscriptionRebrandingFeatureToggle: SubscriptionRebrandingFeatureToggle,
6365
) : AccessRevokedDialog {
6466

6567
private val preferences: SharedPreferences by lazy {
@@ -90,10 +92,15 @@ class RealAccessRevokedDialog @Inject constructor(
9092
if (boundActivity == activity) return
9193

9294
boundActivity = activity
95+
val messageRes = if (subscriptionRebrandingFeatureToggle.isSubscriptionRebrandingEnabled()) {
96+
R.string.netpDialogVpnAccessRevokedBodyRebranding
97+
} else {
98+
R.string.netpDialogVpnAccessRevokedBody
99+
}
93100

94101
TextAlertDialogBuilder(activity)
95102
.setTitle(R.string.netpDialogVpnAccessRevokedTitle)
96-
.setMessage(R.string.netpDialogVpnAccessRevokedBody)
103+
.setMessage(messageRes)
97104
.setPositiveButton(R.string.netpDialogVpnAccessRevokedPositiveAction)
98105
.setNegativeButton(R.string.netpDialogVpnAccessRevokedNegativeAction)
99106
.addEventListener(

network-protection/network-protection-impl/src/main/res/values/donottranslate.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@
1414
~ limitations under the License.
1515
-->
1616

17-
<resources></resources>
17+
<resources>
18+
<!-- Privacy Pro Rebranding-->
19+
<string name="netpNotificationVpnAccessRevokedRebranding">VPN disconnected due to expired subscription. Subscribe to DuckDuckGo to reconnect the VPN.</string>
20+
<string name="netpDialogVpnAccessRevokedBodyRebranding">Subscribe to DuckDuckGo to reconnect the VPN.</string>
21+
</resources>

subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/settings/plugins/SubsSettingsPlugins.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ class ProSettingsTitle @Inject constructor(
3737
) : ProSettingsPlugin {
3838
override fun getView(context: Context): View {
3939
return SectionHeaderListItem(context).apply {
40-
if (subscriptionRebrandingFeatureToggle.isSubscriptionRebrandingEnabled()) {
41-
primaryText = context.getString(R.string.subscriptionSettingSectionTitle)
40+
primaryText = if (subscriptionRebrandingFeatureToggle.isSubscriptionRebrandingEnabled()) {
41+
context.getString(R.string.subscriptionSettingSectionTitle)
4242
} else {
43-
primaryText = context.getString(R.string.privacyPro)
43+
context.getString(R.string.privacyPro)
4444
}
4545
}
4646
}

0 commit comments

Comments
 (0)