Skip to content

Commit d4c3c16

Browse files
authored
Add message after 14 days (#6501)
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1210812799220024?focus=true ### Description This PR adds a new "Set as Default Browser" message to the new tab page. The message is shown as a low-priority message when the user reaches Stage 3 of the default browser prompts flow. The implementation includes: - Added a new `showSetAsDefaultMessage` state flow to track whether to show the message - Created a new `STAGE_3` in the flow stages progression - Implemented a `LowPriorityMessagingModel` to handle low-priority messages on the new tab page - Added UI components to display the default browser message with appropriate actions - Added pixel tracking for message impressions and interactions ### Steps to test this PR _Default Browser Message_ - [x] Make sure the device has time set automatically. - [x] Use this privacy config for easy testing -> https://www.jsonblob.com/api/1395437188749123584. The easiest way would be to set this in `privacy-config/privacy-config-api/src/main/java/com/duckduckgo/privacy/config/api/PrivacyFeatureName.kt`: `const val PRIVACY_REMOTE_CONFIG_URL = "https://www.jsonblob.com/api/1395437188749123584"` - [x] Build and install from this branch. - [x] Skip onboarding. - [x] Change time on device. Advance it by one day. - [x] Open the app and notice the prompt. Tap on `Not Now`. - [x] Close the app. - [x] Change time on device. Advance it by one day. - [x] Open the app and notice the prompt. Tap on `Not Now`. - [x] Close the app. - [x] Change time on device. Advance it by one day. - [x] Open the app and notice the message displayed. It looks like a RMF. Check the below: Icon at the top (middle): Yellow phone with the green check. Title: DuckDuckGo isn't your default browser. Get more protection. Left button (ghost): Don’t Ask Again Right button (primary): Set As Default Top right button: X - [x] Filter logcat by `m_set-as-default_message` and notice this log: `Pixel sent: m_set-as-default_message_impression with params: {} {}` - [x] The message is still there even of you put the app in background or close and reopen the app. - [x] Tap on `Set As Default` and the system screen to choose the browser is shown. The message disappears and is never shown again. - [x] Notice this in logcat: `Pixel sent: m_set-as-default_message_click with params: {} {}` Before running any other tests: - ensure DDG in not the default browser - clear the storage of the app - set the device to have time automatically set Other tests: 1/ Dismiss the message with `X`: - [x] Repeat all steps from the beginning and notice you are at the point you see the message on screen. - [x] Tap on `X` and notice the message disappears and is never shown again. - [x] Filter logcat by `m_set-as-default_message` and notice this log: `Pixel sent: m_set-as-default_message_dismissed with params: {} {}` 2/ Dismiss the message with `Don’t Ask Again`: - [x] Repeat all steps from the beginning and notice you are at the point you see the message on screen. - [x] Tap on `Don’t Ask Again` and notice the message disappears and is never shown again. - [x] Filter logcat by `m_set-as-default_message` and notice this log: `Pixel sent: m_set-as-default_message_dismissed with params: {} {}` 3/ If a RMF should be shown, this message does not appear at all. Once the RMF is dismissed, the message is shown. - [ ] Use this RMF config for easy testing -> https://www.jsonblob.com/api/1400852242860531712. The easiest way would be to set this in `remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/network/RemoteMessagingService.kt`: `@GET("https://www.jsonblob.com/api/1400852242860531712")` - [x] Repeat all steps from the beginning and DO NOT DISMISS the RMF. - [x] When you are at the point to see the message on screen, you'll still see the RMF. - [x] Dismiss the RMF and notice you see the message. ### UI changes | No message | Message only | | ------ | ----- | |<img width="1080" height="2400" alt="no_message" src="https://github.com/user-attachments/assets/64525bf0-1ca6-4655-b1b8-3912de8c9ae6" />|<img width="1080" height="2400" alt="message" src="https://github.com/user-attachments/assets/52b75fd6-57c1-48ba-83ba-ab03922c223a" />| | Message and AppTP | Message and favorites and AppTP | | ------ | ----- | |<img width="1080" height="2400" alt="apptp_message" src="https://github.com/user-attachments/assets/2f058092-5593-49af-9936-90466e74dbf9" />|<img width="1080" height="2400" alt="apptp_favorites_message" src="https://github.com/user-attachments/assets/68a1bd63-7583-4265-bd00-2f8971711084" />|
1 parent 5eabeb3 commit d4c3c16

File tree

14 files changed

+579
-121
lines changed

14 files changed

+579
-121
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/defaultbrowsing/prompts/store/DefaultBrowserPromptsPrefsDataStoreImplTest.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import androidx.datastore.core.DataStore
2020
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
2121
import androidx.datastore.preferences.core.Preferences
2222
import androidx.test.ext.junit.runners.AndroidJUnit4
23-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage
23+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage
2424
import com.duckduckgo.common.test.CoroutineTestRule
2525
import java.io.File
2626
import kotlinx.coroutines.flow.first
@@ -64,22 +64,22 @@ class DefaultBrowserPromptsPrefsDataStoreImplTest {
6464
fun whenExperimentInitializedThenDefaultValueIsNotEnrolled() = runTest {
6565
val testee = DefaultBrowserPromptsPrefsDataStoreImpl(testDataStore, coroutinesTestRule.testDispatcherProvider)
6666

67-
assertEquals(testee.experimentStage.first(), ExperimentStage.NOT_ENROLLED)
67+
assertEquals(testee.stage.first(), Stage.NOT_ENROLLED)
6868
}
6969

7070
@Test
7171
fun whenExperimentStageIsUpdatedThenValueIsPropagated() = runTest {
7272
val testee = DefaultBrowserPromptsPrefsDataStoreImpl(testDataStore, coroutinesTestRule.testDispatcherProvider)
7373
val expectedUpdates = listOf(
74-
ExperimentStage.NOT_ENROLLED,
75-
ExperimentStage.STAGE_1,
74+
Stage.NOT_ENROLLED,
75+
Stage.STAGE_1,
7676
)
77-
val actualUpdates = mutableListOf<ExperimentStage>()
77+
val actualUpdates = mutableListOf<Stage>()
7878
coroutinesTestRule.testScope.launch {
79-
testee.experimentStage.toList(actualUpdates)
79+
testee.stage.toList(actualUpdates)
8080
}
8181

82-
testee.storeExperimentStage(ExperimentStage.STAGE_1)
82+
testee.storeExperimentStage(Stage.STAGE_1)
8383

8484
assertEquals(expectedUpdates, actualUpdates)
8585
}

app/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/prompts/AdditionalDefaultBrowserPrompts.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface AdditionalDefaultBrowserPrompts {
2424

2525
val highlightPopupMenu: StateFlow<Boolean>
2626
val showSetAsDefaultPopupMenuItem: StateFlow<Boolean>
27+
val showSetAsDefaultMessage: StateFlow<Boolean>
2728
val commands: Flow<Command>
2829

2930
fun onPopupMenuLaunched()

app/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/prompts/AdditionalDefaultBrowserPromptsImpl.kt

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ import com.duckduckgo.app.browser.defaultbrowsing.prompts.AdditionalDefaultBrows
2828
import com.duckduckgo.app.browser.defaultbrowsing.prompts.AdditionalDefaultBrowserPrompts.SetAsDefaultActionTrigger.MENU
2929
import com.duckduckgo.app.browser.defaultbrowsing.prompts.AdditionalDefaultBrowserPrompts.SetAsDefaultActionTrigger.UNKNOWN
3030
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore
31-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.CONVERTED
32-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.ENROLLED
33-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.NOT_ENROLLED
34-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.STAGE_1
35-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.STAGE_2
36-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.STOPPED
31+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.CONVERTED
32+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.ENROLLED
33+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.NOT_ENROLLED
34+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.STAGE_1
35+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.STAGE_2
36+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.STAGE_3
37+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.STOPPED
3738
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.ExperimentAppUsageRepository
3839
import com.duckduckgo.app.di.AppCoroutineScope
3940
import com.duckduckgo.app.global.DefaultRoleBrowserDialog
@@ -98,7 +99,7 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
9899
private val experimentAppUsageRepository: ExperimentAppUsageRepository,
99100
private val userStageStore: UserStageStore,
100101
private val defaultBrowserPromptsDataStore: DefaultBrowserPromptsDataStore,
101-
private val stageEvaluator: DefaultBrowserPromptsExperimentStageEvaluator,
102+
private val stageEvaluator: DefaultBrowserPromptsFlowStageEvaluator,
102103
private val pixel: Pixel,
103104
moshi: Moshi,
104105
) : AdditionalDefaultBrowserPrompts, MainProcessLifecycleObserver, PrivacyConfigCallbackPlugin {
@@ -120,19 +121,27 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
120121
initialValue = false,
121122
)
122123

124+
override val showSetAsDefaultMessage: StateFlow<Boolean> = defaultBrowserPromptsDataStore.showSetAsDefaultMessage.stateIn(
125+
scope = appCoroutineScope,
126+
started = SharingStarted.Lazily,
127+
initialValue = false,
128+
)
129+
123130
/**
124131
* Model used to parse remote config setting. All values are integer strings, for example "1" or "20".
125132
*/
126133
@VisibleForTesting
127134
data class FeatureSettingsConfigModel(
128135
val activeDaysUntilStage1: String,
129136
val activeDaysUntilStage2: String,
137+
val activeDaysUntilStage3: String,
130138
val activeDaysUntilStop: String,
131139
)
132140

133141
private data class FeatureSettings(
134142
val activeDaysUntilStage1: Int,
135143
val activeDaysUntilStage2: Int,
144+
val activeDaysUntilStage3: Int,
136145
val activeDaysUntilStop: Int,
137146
)
138147

@@ -190,7 +199,7 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
190199
return
191200
}
192201

193-
val isStopped = defaultBrowserPromptsDataStore.experimentStage.firstOrNull() == STOPPED
202+
val isStopped = defaultBrowserPromptsDataStore.stage.firstOrNull() == STOPPED
194203
logcat { "evaluate: has stopped = $isStopped" }
195204
if (isStopped) {
196205
return
@@ -209,13 +218,13 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
209218

210219
logcat { "evaluate: is default browser = $isDefaultBrowser" }
211220

212-
val hasConvertedBefore = defaultBrowserPromptsDataStore.experimentStage.firstOrNull() == CONVERTED
221+
val hasConvertedBefore = defaultBrowserPromptsDataStore.stage.firstOrNull() == CONVERTED
213222
logcat { "evaluate: has converted before = $hasConvertedBefore" }
214223
if (hasConvertedBefore) {
215224
return
216225
}
217226

218-
val currentStage = defaultBrowserPromptsDataStore.experimentStage.firstOrNull()
227+
val currentStage = defaultBrowserPromptsDataStore.stage.firstOrNull()
219228
logcat { "evaluate: current stage = $currentStage" }
220229
val newStage = if (isDefaultBrowser) {
221230
logcat { "evaluate: new stage is CONVERTED" }
@@ -260,7 +269,16 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
260269
}
261270

262271
STAGE_2 -> {
263-
if (appActiveDaysUsedSinceEnrollment >= configSettings.activeDaysUntilStop) {
272+
if (appActiveDaysUsedSinceEnrollment >= configSettings.activeDaysUntilStage3) {
273+
STAGE_3
274+
} else {
275+
null
276+
}
277+
}
278+
279+
STAGE_3 -> {
280+
val stage3Finished = defaultBrowserPromptsDataStore.showSetAsDefaultMessage.firstOrNull() == false
281+
if (stage3Finished) {
264282
STOPPED
265283
} else {
266284
null
@@ -282,6 +300,7 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
282300
}
283301
defaultBrowserPromptsDataStore.storeShowSetAsDefaultPopupMenuItemState(action.showSetAsDefaultPopupMenuItem)
284302
defaultBrowserPromptsDataStore.storeHighlightPopupMenuState(action.highlightPopupMenu)
303+
defaultBrowserPromptsDataStore.storeShowSetAsDefaultMessageState(action.showMessage)
285304
}
286305
}
287306

@@ -380,7 +399,7 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
380399
}
381400

382401
private fun fireConversionPixel(trigger: SetAsDefaultActionTrigger) = appCoroutineScope.launch {
383-
val stage = defaultBrowserPromptsDataStore.experimentStage.firstOrNull().toString().lowercase()
402+
val stage = defaultBrowserPromptsDataStore.stage.firstOrNull().toString().lowercase()
384403
val triggerValue = trigger.toString().lowercase()
385404
logcat { "fireConversionPixel: pixelName = ${AppPixelName.SET_AS_DEFAULT_SYSTEM_DIALOG_CLICK} - stage = $stage - trigger = $triggerValue" }
386405
pixel.fire(
@@ -393,7 +412,7 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
393412
}
394413

395414
private fun fireInteractionPixel(pixelName: AppPixelName) = appCoroutineScope.launch {
396-
val stage = defaultBrowserPromptsDataStore.experimentStage.firstOrNull().toString().lowercase()
415+
val stage = defaultBrowserPromptsDataStore.stage.firstOrNull().toString().lowercase()
397416
logcat { "fireInteractionPixel pixelName = $pixelName - stage = $stage" }
398417
pixel.fire(
399418
pixel = pixelName,
@@ -407,6 +426,7 @@ class AdditionalDefaultBrowserPromptsImpl @Inject constructor(
407426
private fun FeatureSettingsConfigModel.toFeatureSettings() = FeatureSettings(
408427
activeDaysUntilStage1 = activeDaysUntilStage1.toInt(),
409428
activeDaysUntilStage2 = activeDaysUntilStage2.toInt(),
429+
activeDaysUntilStage3 = activeDaysUntilStage3.toInt(),
410430
activeDaysUntilStop = activeDaysUntilStop.toInt(),
411431
)
412432

app/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/prompts/DefaultBrowserPromptsExperimentVariants.kt

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,58 +16,70 @@
1616

1717
package com.duckduckgo.app.browser.defaultbrowsing.prompts
1818

19-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage
20-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.CONVERTED
21-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.ENROLLED
22-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.NOT_ENROLLED
23-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.STAGE_1
24-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.STAGE_2
25-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.STOPPED
19+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage
20+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.CONVERTED
21+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.ENROLLED
22+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.NOT_ENROLLED
23+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.STAGE_1
24+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.STAGE_2
25+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.STAGE_3
26+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.STOPPED
2627
import com.duckduckgo.di.scopes.AppScope
2728
import com.squareup.anvil.annotations.ContributesBinding
2829
import javax.inject.Inject
2930

30-
data class DefaultBrowserPromptsExperimentStageAction(
31+
data class DefaultBrowserPromptsFlowStageAction(
3132
val showMessageDialog: Boolean,
3233
val showSetAsDefaultPopupMenuItem: Boolean,
3334
val highlightPopupMenu: Boolean,
35+
val showMessage: Boolean,
3436
) {
3537
companion object {
36-
val disableAll = DefaultBrowserPromptsExperimentStageAction(
38+
val disableAll = DefaultBrowserPromptsFlowStageAction(
3739
showMessageDialog = false,
3840
showSetAsDefaultPopupMenuItem = false,
3941
highlightPopupMenu = false,
42+
showMessage = false,
4043
)
4144
}
4245
}
4346

44-
interface DefaultBrowserPromptsExperimentStageEvaluator {
45-
suspend fun evaluate(newStage: ExperimentStage): DefaultBrowserPromptsExperimentStageAction
47+
interface DefaultBrowserPromptsFlowStageEvaluator {
48+
suspend fun evaluate(newStage: Stage): DefaultBrowserPromptsFlowStageAction
4649
}
4750

4851
@ContributesBinding(AppScope::class)
49-
class DefaultBrowserPromptsExperimentStageEvaluatorImpl @Inject constructor() : DefaultBrowserPromptsExperimentStageEvaluator {
52+
class DefaultBrowserPromptsFlowStageEvaluatorImpl @Inject constructor() : DefaultBrowserPromptsFlowStageEvaluator {
5053

51-
override suspend fun evaluate(newStage: ExperimentStage): DefaultBrowserPromptsExperimentStageAction =
54+
override suspend fun evaluate(newStage: Stage): DefaultBrowserPromptsFlowStageAction =
5255
when (newStage) {
53-
NOT_ENROLLED -> DefaultBrowserPromptsExperimentStageAction.disableAll
56+
NOT_ENROLLED -> DefaultBrowserPromptsFlowStageAction.disableAll
5457

55-
ENROLLED -> DefaultBrowserPromptsExperimentStageAction.disableAll
58+
ENROLLED -> DefaultBrowserPromptsFlowStageAction.disableAll
5659

57-
STAGE_1 -> DefaultBrowserPromptsExperimentStageAction(
60+
STAGE_1 -> DefaultBrowserPromptsFlowStageAction(
5861
showMessageDialog = true,
5962
showSetAsDefaultPopupMenuItem = true,
6063
highlightPopupMenu = true,
64+
showMessage = false,
6165
)
6266

63-
STAGE_2 -> DefaultBrowserPromptsExperimentStageAction(
67+
STAGE_2 -> DefaultBrowserPromptsFlowStageAction(
6468
showMessageDialog = true,
6569
showSetAsDefaultPopupMenuItem = true,
6670
highlightPopupMenu = true,
71+
showMessage = false,
6772
)
6873

69-
STOPPED -> DefaultBrowserPromptsExperimentStageAction.disableAll
74+
STAGE_3 -> DefaultBrowserPromptsFlowStageAction(
75+
showMessageDialog = false,
76+
showSetAsDefaultPopupMenuItem = false,
77+
highlightPopupMenu = false,
78+
showMessage = true,
79+
)
80+
81+
STOPPED -> DefaultBrowserPromptsFlowStageAction.disableAll
7082

71-
CONVERTED -> DefaultBrowserPromptsExperimentStageAction.disableAll
83+
CONVERTED -> DefaultBrowserPromptsFlowStageAction.disableAll
7284
}
7385
}

app/src/main/java/com/duckduckgo/app/browser/defaultbrowsing/prompts/store/DefaultBrowserPromptsDataStore.kt

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
2222
import androidx.datastore.preferences.core.edit
2323
import androidx.datastore.preferences.core.stringPreferencesKey
2424
import com.duckduckgo.app.browser.defaultbrowsing.prompts.di.DefaultBrowserPrompts
25-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage
26-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.ExperimentStage.NOT_ENROLLED
25+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage
26+
import com.duckduckgo.app.browser.defaultbrowsing.prompts.store.DefaultBrowserPromptsDataStore.Stage.NOT_ENROLLED
2727
import com.duckduckgo.common.utils.DispatcherProvider
2828
import com.duckduckgo.di.scopes.AppScope
2929
import com.squareup.anvil.annotations.ContributesBinding
@@ -33,19 +33,22 @@ import kotlinx.coroutines.flow.map
3333
import kotlinx.coroutines.withContext
3434

3535
interface DefaultBrowserPromptsDataStore {
36-
val experimentStage: Flow<ExperimentStage>
36+
val stage: Flow<Stage>
3737
val showSetAsDefaultPopupMenuItem: Flow<Boolean>
3838
val highlightPopupMenu: Flow<Boolean>
39+
val showSetAsDefaultMessage: Flow<Boolean>
3940

40-
suspend fun storeExperimentStage(stage: ExperimentStage)
41+
suspend fun storeExperimentStage(stage: Stage)
4142
suspend fun storeShowSetAsDefaultPopupMenuItemState(show: Boolean)
4243
suspend fun storeHighlightPopupMenuState(highlight: Boolean)
44+
suspend fun storeShowSetAsDefaultMessageState(show: Boolean)
4345

44-
enum class ExperimentStage {
46+
enum class Stage {
4547
NOT_ENROLLED,
4648
ENROLLED,
4749
STAGE_1,
4850
STAGE_2,
51+
STAGE_3,
4952
STOPPED,
5053
CONVERTED,
5154
}
@@ -60,10 +63,11 @@ class DefaultBrowserPromptsPrefsDataStoreImpl @Inject constructor(
6063
private const val PREF_KEY_EXPERIMENT_STAGE_ID = "additional_default_browser_prompts_experiment_stage_id"
6164
private const val PREF_KEY_SHOW_OVERFLOW_MENU_ITEM = "additional_default_browser_prompts_show_overflow_menu_item"
6265
private const val PREF_KEY_HIGHLIGHT_OVERFLOW_MENU_ICON = "additional_default_browser_prompts_highlight_overflow_menu_icon"
66+
private const val PREF_KEY_SHOW_SET_AS_DEFAULT_MESSAGE = "additional_default_browser_prompts_show_set_as_default_message"
6367
}
6468

65-
override val experimentStage: Flow<ExperimentStage> = store.data.map { preferences ->
66-
preferences[stringPreferencesKey(PREF_KEY_EXPERIMENT_STAGE_ID)]?.let { ExperimentStage.valueOf(it) } ?: NOT_ENROLLED
69+
override val stage: Flow<Stage> = store.data.map { preferences ->
70+
preferences[stringPreferencesKey(PREF_KEY_EXPERIMENT_STAGE_ID)]?.let { Stage.valueOf(it) } ?: NOT_ENROLLED
6771
}
6872

6973
override val showSetAsDefaultPopupMenuItem: Flow<Boolean> = store.data.map { preferences ->
@@ -74,7 +78,11 @@ class DefaultBrowserPromptsPrefsDataStoreImpl @Inject constructor(
7478
preferences[booleanPreferencesKey(PREF_KEY_HIGHLIGHT_OVERFLOW_MENU_ICON)] ?: false
7579
}
7680

77-
override suspend fun storeExperimentStage(stage: ExperimentStage) {
81+
override val showSetAsDefaultMessage: Flow<Boolean> = store.data.map { preferences ->
82+
preferences[booleanPreferencesKey(PREF_KEY_SHOW_SET_AS_DEFAULT_MESSAGE)] ?: false
83+
}
84+
85+
override suspend fun storeExperimentStage(stage: Stage) {
7886
withContext(dispatchers.io()) {
7987
store.edit { preferences ->
8088
preferences[stringPreferencesKey(PREF_KEY_EXPERIMENT_STAGE_ID)] = stage.name
@@ -97,4 +105,12 @@ class DefaultBrowserPromptsPrefsDataStoreImpl @Inject constructor(
97105
}
98106
}
99107
}
108+
109+
override suspend fun storeShowSetAsDefaultMessageState(show: Boolean) {
110+
withContext(dispatchers.io()) {
111+
store.edit { preferences ->
112+
preferences[booleanPreferencesKey(PREF_KEY_SHOW_SET_AS_DEFAULT_MESSAGE)] = show
113+
}
114+
}
115+
}
100116
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.browser.newtab
18+
19+
import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.Command
20+
21+
interface LowPriorityMessagingModel {
22+
suspend fun getMessage(): LowPriorityMessage?
23+
fun onMessageShown()
24+
fun getPrimaryButtonCommand(): Command?
25+
}

0 commit comments

Comments
 (0)