Skip to content

Commit 4b7de35

Browse files
authored
PIR: Add support for basic onboarding flow (#6583)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1203581873609357/task/1211052058790440?focus=true ### Description See task description ### Steps to test this PR https://app.asana.com/1/137249556945/project/1203581873609357/task/1211052058790442?focus=true ### UI changes No UI changes
1 parent f27a886 commit 4b7de35

14 files changed

+723
-67
lines changed

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/PirDashboardWebConstants.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ object PirDashboardWebConstants {
2121
const val CUSTOM_UA = "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko)" +
2222
" Version/4.0 Chrome/124.0.0.0 Mobile DuckDuckGo/5 Safari/537.36"
2323

24-
internal const val SCRIPT_API_VERSION = 10
24+
internal const val SCRIPT_API_VERSION = 11
2525
internal const val SCRIPT_CONTEXT_NAME = "dbpui"
2626
internal const val SCRIPT_FEATURE_NAME = "dbpuiCommunication"
2727
internal const val MESSAGE_CALLBACK = "messageCallback"
@@ -30,4 +30,9 @@ object PirDashboardWebConstants {
3030

3131
internal const val PARAM_SUCCESS = "success"
3232
internal const val PARAM_VERSION = "version"
33+
internal const val PARAM_FIRST_NAME = "first"
34+
internal const val PARAM_MIDDLE_NAME = "middle"
35+
internal const val PARAM_LAST_NAME = "last"
36+
internal const val PARAM_CITY = "city"
37+
internal const val PARAM_STATE = "state"
3338
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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.pir.impl.dashboard.messaging.handlers
18+
19+
import com.duckduckgo.di.scopes.ActivityScope
20+
import com.duckduckgo.js.messaging.api.JsMessage
21+
import com.duckduckgo.js.messaging.api.JsMessageCallback
22+
import com.duckduckgo.js.messaging.api.JsMessaging
23+
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
24+
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
25+
import com.duckduckgo.pir.impl.models.Address
26+
import com.squareup.anvil.annotations.ContributesMultibinding
27+
import javax.inject.Inject
28+
import logcat.logcat
29+
30+
/**
31+
* Handles the message from Web to add an address the user inputted to the current user profile.
32+
*/
33+
@ContributesMultibinding(
34+
scope = ActivityScope::class,
35+
boundType = PirWebJsMessageHandler::class,
36+
)
37+
class PirWebAddAddressToCurrentUserProfileMessageHandler @Inject constructor(
38+
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
39+
) : PirWebJsMessageHandler() {
40+
override val messageNames: List<PirDashboardWebMessages> =
41+
listOf(PirDashboardWebMessages.ADD_ADDRESS_TO_CURRENT_USER_PROFILE)
42+
43+
override fun process(
44+
jsMessage: JsMessage,
45+
jsMessaging: JsMessaging,
46+
jsMessageCallback: JsMessageCallback?,
47+
) {
48+
logcat { "PIR-WEB: PirWebAddAddressToCurrentUserProfileMessageHandler: process $jsMessage" }
49+
50+
val city = jsMessage.params.getStringParam("city")
51+
val state = jsMessage.params.getStringParam("state")
52+
53+
// attempting to add an empty address should return success=false
54+
if (city == null || state == null) {
55+
logcat { "PIR-WEB: PirWebAddAddressToCurrentUserProfileMessageHandler: missing city and/or state" }
56+
jsMessaging.sendPirResponse(
57+
jsMessage = jsMessage,
58+
success = false,
59+
)
60+
return
61+
}
62+
63+
// attempting to add a duplicate address should return success=false
64+
if (pirWebOnboardingStateHolder.addresses.any { it.city == city && it.state == state }) {
65+
logcat { "PIR-WEB: PirWebAddAddressToCurrentUserProfileMessageHandler: address already exists" }
66+
jsMessaging.sendPirResponse(
67+
jsMessage = jsMessage,
68+
success = false,
69+
)
70+
return
71+
}
72+
73+
pirWebOnboardingStateHolder.addresses.add(
74+
Address(
75+
city = city,
76+
state = state,
77+
),
78+
)
79+
80+
jsMessaging.sendPirResponse(
81+
jsMessage = jsMessage,
82+
success = true,
83+
)
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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.pir.impl.dashboard.messaging.handlers
18+
19+
import com.duckduckgo.di.scopes.ActivityScope
20+
import com.duckduckgo.js.messaging.api.JsMessage
21+
import com.duckduckgo.js.messaging.api.JsMessageCallback
22+
import com.duckduckgo.js.messaging.api.JsMessaging
23+
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebConstants
24+
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
25+
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder
26+
import com.squareup.anvil.annotations.ContributesMultibinding
27+
import javax.inject.Inject
28+
import logcat.logcat
29+
30+
/**
31+
* Handles the message from Web to add a name the user inputted to the current user profile.
32+
*/
33+
@ContributesMultibinding(
34+
scope = ActivityScope::class,
35+
boundType = PirWebJsMessageHandler::class,
36+
)
37+
class PirWebAddNameToCurrentUserProfileMessageHandler @Inject constructor(
38+
private val pirWebOnboardingStateHolder: PirWebOnboardingStateHolder,
39+
) : PirWebJsMessageHandler() {
40+
41+
override val messageNames: List<PirDashboardWebMessages> =
42+
listOf(PirDashboardWebMessages.ADD_NAME_TO_CURRENT_USER_PROFILE)
43+
44+
override fun process(
45+
jsMessage: JsMessage,
46+
jsMessaging: JsMessaging,
47+
jsMessageCallback: JsMessageCallback?,
48+
) {
49+
logcat { "PIR-WEB: PirWebAddNameToCurrentUserProfileMessageHandler: process $jsMessage" }
50+
51+
val firstName = jsMessage.params.getStringParam(PirDashboardWebConstants.PARAM_FIRST_NAME)
52+
val middleName = jsMessage.params.getStringParam(PirDashboardWebConstants.PARAM_MIDDLE_NAME)
53+
val lastName = jsMessage.params.getStringParam(PirDashboardWebConstants.PARAM_LAST_NAME)
54+
55+
// attempting to add an empty name should return success=false
56+
if (firstName == null || lastName == null) {
57+
logcat { "PIR-WEB: PirWebAddNameToCurrentUserProfileMessageHandler: missing first and/or last names" }
58+
jsMessaging.sendPirResponse(
59+
jsMessage = jsMessage,
60+
success = false,
61+
)
62+
return
63+
}
64+
65+
// attempting to add a duplicate name should return success=false
66+
if (pirWebOnboardingStateHolder.names.any { it.firstName == firstName && it.middleName == middleName && it.lastName == lastName }) {
67+
logcat { "PIR-WEB: PirWebAddNameToCurrentUserProfileMessageHandler: duplicate name detected" }
68+
jsMessaging.sendPirResponse(
69+
jsMessage = jsMessage,
70+
success = false,
71+
)
72+
return
73+
}
74+
75+
// Add the name to the current user profile
76+
pirWebOnboardingStateHolder.names.add(
77+
PirWebOnboardingStateHolder.Name(
78+
firstName = firstName,
79+
middleName = middleName,
80+
lastName = lastName,
81+
),
82+
)
83+
84+
jsMessaging.sendPirResponse(
85+
jsMessage = jsMessage,
86+
success = true,
87+
)
88+
}
89+
}

pir/pir-impl/src/main/java/com/duckduckgo/pir/impl/dashboard/messaging/handlers/PirWebGetCurrentUserProfileMessageHandler.kt

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@
1616

1717
package com.duckduckgo.pir.impl.dashboard.messaging.handlers
1818

19+
import com.duckduckgo.app.di.AppCoroutineScope
20+
import com.duckduckgo.common.utils.DispatcherProvider
1921
import com.duckduckgo.di.scopes.ActivityScope
20-
import com.duckduckgo.js.messaging.api.JsCallbackData
2122
import com.duckduckgo.js.messaging.api.JsMessage
2223
import com.duckduckgo.js.messaging.api.JsMessageCallback
2324
import com.duckduckgo.js.messaging.api.JsMessaging
2425
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebConstants
2526
import com.duckduckgo.pir.impl.dashboard.messaging.PirDashboardWebMessages
27+
import com.duckduckgo.pir.impl.dashboard.state.PirWebOnboardingStateHolder.Name
28+
import com.duckduckgo.pir.impl.store.PirRepository
2629
import com.squareup.anvil.annotations.ContributesMultibinding
2730
import javax.inject.Inject
31+
import kotlinx.coroutines.CoroutineScope
32+
import kotlinx.coroutines.launch
2833
import logcat.logcat
34+
import org.json.JSONArray
2935
import org.json.JSONObject
3036

3137
/**
@@ -36,33 +42,72 @@ import org.json.JSONObject
3642
scope = ActivityScope::class,
3743
boundType = PirWebJsMessageHandler::class,
3844
)
39-
class PirWebGetCurrentUserProfileMessageHandler @Inject constructor() :
45+
class PirWebGetCurrentUserProfileMessageHandler @Inject constructor(
46+
private val repository: PirRepository,
47+
private val dispatcherProvider: DispatcherProvider,
48+
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
49+
) :
4050
PirWebJsMessageHandler() {
4151

42-
override val methods: List<String> =
43-
listOf(PirDashboardWebMessages.GET_CURRENT_USER_PROFILE.messageName)
52+
override val messageNames: List<PirDashboardWebMessages> = listOf(PirDashboardWebMessages.GET_CURRENT_USER_PROFILE)
4453

4554
override fun process(
4655
jsMessage: JsMessage,
4756
jsMessaging: JsMessaging,
4857
jsMessageCallback: JsMessageCallback?,
4958
) {
50-
logcat { "PIR-WEB: GetCurrentUserProfileMessageHandler: process $jsMessage" }
59+
logcat { "PIR-WEB: PirWebGetCurrentUserProfileMessageHandler: process $jsMessage" }
5160

52-
jsMessaging.onResponse(
53-
JsCallbackData(
54-
params = JSONObject().apply {
55-
put(PirDashboardWebConstants.PARAM_SUCCESS, false)
56-
put(
57-
PirDashboardWebConstants.PARAM_VERSION,
58-
PirDashboardWebConstants.SCRIPT_API_VERSION,
59-
)
60-
// TODO: Replace with actual user profile data
61-
},
62-
featureName = jsMessage.featureName,
63-
method = jsMessage.method,
64-
id = jsMessage.id ?: "",
65-
),
66-
)
61+
appCoroutineScope.launch(dispatcherProvider.io()) {
62+
val profiles = repository.getUserProfileQueries()
63+
64+
if (profiles.isEmpty()) {
65+
logcat { "PIR-WEB: GetCurrentUserProfileMessageHandler: no user profiles found" }
66+
jsMessaging.sendPirResponse(
67+
jsMessage = jsMessage,
68+
success = true,
69+
)
70+
return@launch
71+
}
72+
73+
val names = profiles.map { Name(it.firstName, it.middleName, it.lastName) }
74+
val addresses = profiles.map { it.addresses }.flatten()
75+
val birthYear = profiles.firstOrNull()?.birthYear ?: 0
76+
77+
jsMessaging.sendPirResponse(
78+
jsMessage = jsMessage,
79+
success = true,
80+
customParams = mapOf(
81+
PARAM_ADDRESSES to JSONArray().apply {
82+
addresses.forEach { address ->
83+
put(
84+
JSONObject().apply {
85+
put(PirDashboardWebConstants.PARAM_CITY, address.city)
86+
put(PirDashboardWebConstants.PARAM_STATE, address.state)
87+
},
88+
)
89+
}
90+
},
91+
PARAM_BIRTH_YEAR to birthYear,
92+
PARAM_NAMES to JSONArray().apply {
93+
names.forEach { name ->
94+
put(
95+
JSONObject().apply {
96+
put(PirDashboardWebConstants.PARAM_FIRST_NAME, name.firstName)
97+
put(PirDashboardWebConstants.PARAM_MIDDLE_NAME, name.middleName ?: "")
98+
put(PirDashboardWebConstants.PARAM_LAST_NAME, name.lastName)
99+
},
100+
)
101+
}
102+
},
103+
),
104+
)
105+
}
106+
}
107+
108+
companion object {
109+
private const val PARAM_ADDRESSES = "addresses"
110+
private const val PARAM_BIRTH_YEAR = "birthYear"
111+
private const val PARAM_NAMES = "names"
67112
}
68113
}

0 commit comments

Comments
 (0)