Skip to content

Commit e453707

Browse files
authored
Merge pull request #10680 from woocommerce/issue/10630-location-selection
Blaze: Target location selection
2 parents 7d59059 + 356d53b commit e453707

15 files changed

+778
-131
lines changed

WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/FlowExtensions.kt

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,47 @@ package com.woocommerce.android.extensions
33
import kotlinx.coroutines.flow.Flow
44

55
@Suppress("LongParameterList")
6-
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
6+
inline fun <T1, T2, T3, T4, T5, T6, R> combine(
77
flow: Flow<T1>,
88
flow2: Flow<T2>,
99
flow3: Flow<T3>,
1010
flow4: Flow<T4>,
1111
flow5: Flow<T5>,
1212
flow6: Flow<T6>,
13-
flow7: Flow<T7>,
14-
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
13+
crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
1514
): Flow<R> {
16-
return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
15+
return kotlinx.coroutines.flow.combine(
16+
flow,
17+
flow2,
18+
flow3,
19+
flow4,
20+
flow5,
21+
flow6,
22+
) { args: Array<*> ->
1723
@Suppress("UNCHECKED_CAST", "MagicNumber")
1824
transform(
1925
args[0] as T1,
2026
args[1] as T2,
2127
args[2] as T3,
2228
args[3] as T4,
2329
args[4] as T5,
24-
args[5] as T6,
25-
args[6] as T7,
30+
args[5] as T6
2631
)
2732
}
2833
}
2934

3035
@Suppress("LongParameterList")
31-
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
36+
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
3237
flow: Flow<T1>,
3338
flow2: Flow<T2>,
3439
flow3: Flow<T3>,
3540
flow4: Flow<T4>,
3641
flow5: Flow<T5>,
3742
flow6: Flow<T6>,
3843
flow7: Flow<T7>,
39-
flow8: Flow<T8>,
40-
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
44+
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
4145
): Flow<R> {
42-
return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { args: Array<*> ->
46+
return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
4347
@Suppress("UNCHECKED_CAST", "MagicNumber")
4448
transform(
4549
args[0] as T1,
@@ -49,7 +53,6 @@ inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
4953
args[4] as T5,
5054
args[5] as T6,
5155
args[6] as T7,
52-
args[7] as T8,
5356
)
5457
}
5558
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@ class BlazeRepository @Inject constructor(
3535
fun observeDevices() = blazeCampaignsStore.observeBlazeTargetingDevices()
3636
.map { it.map { device -> Device(device.id, device.name) } }
3737

38-
suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingTopics()
38+
suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingDevices()
3939

4040
fun observeInterests() = blazeCampaignsStore.observeBlazeTargetingTopics()
4141
.map { it.map { interest -> Interest(interest.id, interest.description) } }
4242

4343
suspend fun fetchInterests() = blazeCampaignsStore.fetchBlazeTargetingTopics()
4444

45+
suspend fun fetchLocations(query: String) = blazeCampaignsStore.fetchBlazeTargetingLocations(query).model
46+
?.map { location -> Location(location.id, location.name, location.parent?.name, location.type) }
47+
4548
suspend fun getMostRecentCampaign() = blazeCampaignsStore.getMostRecentBlazeCampaign(selectedSite.get())
4649

4750
suspend fun getAdSuggestions(productId: Long): List<AiSuggestionForAd>? {
@@ -91,28 +94,30 @@ class BlazeRepository @Inject constructor(
9194
val durationInDays: Int,
9295
val startDate: Date,
9396
) : Parcelable
94-
95-
@Parcelize
96-
data class Location(
97-
val id: String,
98-
val name: String,
99-
) : Parcelable
100-
101-
@Parcelize
102-
data class Language(
103-
val code: String,
104-
val name: String,
105-
) : Parcelable
106-
107-
@Parcelize
108-
data class Device(
109-
val id: String,
110-
val name: String,
111-
) : Parcelable
112-
113-
@Parcelize
114-
data class Interest(
115-
val id: String,
116-
val description: String,
117-
) : Parcelable
11897
}
98+
99+
@Parcelize
100+
data class Location(
101+
val id: Long,
102+
val name: String,
103+
val parent: String? = null,
104+
val type: String? = null
105+
) : Parcelable
106+
107+
@Parcelize
108+
data class Language(
109+
val code: String,
110+
val name: String,
111+
) : Parcelable
112+
113+
@Parcelize
114+
data class Device(
115+
val id: String,
116+
val name: String,
117+
) : Parcelable
118+
119+
@Parcelize
120+
data class Interest(
121+
val id: String,
122+
val description: String,
123+
) : Parcelable

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdF
1313
import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.EditAdResult
1414
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToBudgetScreen
1515
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToEditAdScreen
16+
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToTargetLocationSelectionScreen
1617
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToTargetSelectionScreen
18+
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetLocationSelectionFragment
19+
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetLocationSelectionViewModel.TargetLocationResult
1720
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionFragment
1821
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionViewModel.TargetSelectionResult
1922
import com.woocommerce.android.ui.compose.composeView
@@ -64,6 +67,12 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() {
6467
event.selectedIds.toTypedArray()
6568
)
6669
)
70+
is NavigateToTargetLocationSelectionScreen -> findNavController().navigateSafely(
71+
BlazeCampaignCreationPreviewFragmentDirections
72+
.actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignTargetLocationSelectionFragment(
73+
event.locations.toTypedArray()
74+
)
75+
)
6776
}
6877
}
6978
}
@@ -75,5 +84,8 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() {
7584
handleResult<TargetSelectionResult>(BlazeCampaignTargetSelectionFragment.BLAZE_TARGET_SELECTION_RESULT) {
7685
viewModel.onTargetSelectionUpdated(it.targetType, it.selectedIds)
7786
}
87+
handleResult<TargetLocationResult>(BlazeCampaignTargetLocationSelectionFragment.BLAZE_TARGET_LOCATION_RESULT) {
88+
viewModel.onTargetLocationsUpdated(it.locations)
89+
}
7890
}
7991
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import com.woocommerce.android.extensions.formatToMMMdd
1010
import com.woocommerce.android.ui.blaze.BlazeRepository
1111
import com.woocommerce.android.ui.blaze.BlazeRepository.Budget
1212
import com.woocommerce.android.ui.blaze.BlazeRepository.CampaignPreview
13-
import com.woocommerce.android.ui.blaze.BlazeRepository.Device
14-
import com.woocommerce.android.ui.blaze.BlazeRepository.Interest
15-
import com.woocommerce.android.ui.blaze.BlazeRepository.Language
16-
import com.woocommerce.android.ui.blaze.BlazeRepository.Location
13+
import com.woocommerce.android.ui.blaze.Device
14+
import com.woocommerce.android.ui.blaze.Interest
15+
import com.woocommerce.android.ui.blaze.Language
16+
import com.woocommerce.android.ui.blaze.Location
1717
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.AdDetails
1818
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading
1919
import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType
@@ -27,6 +27,7 @@ import com.woocommerce.android.viewmodel.ScopedViewModel
2727
import com.woocommerce.android.viewmodel.getStateFlow
2828
import com.woocommerce.android.viewmodel.navArgs
2929
import dagger.hilt.android.lifecycle.HiltViewModel
30+
import kotlinx.coroutines.flow.combine
3031
import kotlinx.coroutines.flow.update
3132
import kotlinx.coroutines.launch
3233
import kotlinx.parcelize.Parcelize
@@ -49,40 +50,57 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
4950
private val languages = blazeRepository.observeLanguages()
5051
private val devices = blazeRepository.observeDevices()
5152
private val interests = blazeRepository.observeInterests()
52-
private val selectedLanguages = savedStateHandle.getStateFlow<List<String>>(
53+
54+
private val selectedLanguageCodes = savedStateHandle.getStateFlow<List<String>>(
5355
scope = viewModelScope,
5456
initialValue = emptyList(),
5557
key = "selectedLanguages"
5658
)
57-
private val selectedDevices = savedStateHandle.getStateFlow<List<String>>(
59+
60+
private val selectedLanguages = combine(languages, selectedLanguageCodes) { languages, selectedCodes ->
61+
languages.filter { it.code in selectedCodes }
62+
}
63+
64+
private val selectedDeviceIds = savedStateHandle.getStateFlow<List<String>>(
5865
scope = viewModelScope,
5966
initialValue = emptyList(),
6067
key = "selectedDevices"
6168
)
62-
private val selectedInterests = savedStateHandle.getStateFlow<List<String>>(
69+
70+
private val selectedDevices = combine(devices, selectedDeviceIds) { devices, selectedIds ->
71+
devices.filter { it.id in selectedIds }
72+
}
73+
private val selectedInterestIds = savedStateHandle.getStateFlow<List<String>>(
6374
scope = viewModelScope,
6475
initialValue = emptyList(),
6576
key = "selectedInterests"
6677
)
6778

79+
private val selectedInterests = combine(interests, selectedInterestIds) { interests, selectedIds ->
80+
interests.filter { it.id in selectedIds }
81+
}
82+
83+
private val selectedLocations = savedStateHandle.getStateFlow<List<Location>>(
84+
scope = viewModelScope,
85+
initialValue = emptyList()
86+
)
87+
6888
val viewState = combine(
6989
adDetails,
7090
budget,
71-
languages,
72-
devices,
73-
interests,
7491
selectedLanguages,
7592
selectedDevices,
76-
selectedInterests
77-
) { adDetails, budget, languages, devices, interests, selectedLanguages, selectedDevices, selectedInterests ->
93+
selectedInterests,
94+
selectedLocations
95+
) { ad, budget, selectedLanguages, selectedDevices, selectedInterests, selectedLocations ->
7896
CampaignPreviewUiState(
79-
adDetails = adDetails,
97+
adDetails = ad,
8098
campaignDetails = campaign.toCampaignDetailsUi(
8199
budget,
82-
languages.filter { it.code in selectedLanguages },
83-
devices.filter { it.id in selectedDevices },
84-
interests.filter { it.id in selectedInterests },
85-
emptyList()
100+
selectedLanguages,
101+
selectedDevices,
102+
selectedInterests,
103+
selectedLocations
86104
)
87105
)
88106
}.asLiveData()
@@ -122,14 +140,18 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
122140
fun onTargetSelectionUpdated(targetType: BlazeTargetType, selectedIds: List<String>) {
123141
launch {
124142
when (targetType) {
125-
LANGUAGE -> selectedLanguages.update { selectedIds }
126-
DEVICE -> selectedDevices.update { selectedIds }
127-
INTEREST -> selectedInterests.update { selectedIds }
143+
LANGUAGE -> selectedLanguageCodes.update { selectedIds }
144+
DEVICE -> selectedDeviceIds.update { selectedIds }
145+
INTEREST -> selectedInterestIds.update { selectedIds }
128146
else -> Unit
129147
}
130148
}
131149
}
132150

151+
fun onTargetLocationsUpdated(locations: List<Location>) {
152+
selectedLocations.update { locations }
153+
}
154+
133155
private fun loadData() {
134156
launch {
135157
blazeRepository.fetchLanguages()
@@ -182,7 +204,9 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
182204
displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location),
183205
displayValue = locations.joinToString { it.name }
184206
.ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) },
185-
onItemSelected = { /* TODO Add location selection */ },
207+
onItemSelected = {
208+
triggerEvent(NavigateToTargetLocationSelectionScreen(locations))
209+
},
186210
),
187211
CampaignDetailItemUi(
188212
displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_interests),
@@ -261,8 +285,13 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
261285
)
262286

263287
object NavigateToBudgetScreen : MultiLiveEvent.Event()
288+
264289
data class NavigateToTargetSelectionScreen(
265290
val targetType: BlazeTargetType,
266291
val selectedIds: List<String>
267292
) : MultiLiveEvent.Event()
293+
294+
data class NavigateToTargetLocationSelectionScreen(
295+
val locations: List<Location>
296+
) : MultiLiveEvent.Event()
268297
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.woocommerce.android.ui.blaze.creation.targets
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.fragment.app.viewModels
8+
import androidx.navigation.fragment.findNavController
9+
import com.woocommerce.android.extensions.navigateBackWithResult
10+
import com.woocommerce.android.ui.base.BaseFragment
11+
import com.woocommerce.android.ui.compose.composeView
12+
import com.woocommerce.android.ui.main.AppBarStatus
13+
import com.woocommerce.android.viewmodel.MultiLiveEvent
14+
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult
15+
import dagger.hilt.android.AndroidEntryPoint
16+
17+
@AndroidEntryPoint
18+
class BlazeCampaignTargetLocationSelectionFragment : BaseFragment() {
19+
companion object {
20+
const val BLAZE_TARGET_LOCATION_RESULT = "blaze_target_location_result"
21+
}
22+
23+
override val activityAppBarStatus: AppBarStatus
24+
get() = AppBarStatus.Hidden
25+
26+
val viewModel: BlazeCampaignTargetLocationSelectionViewModel by viewModels()
27+
28+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
29+
return composeView {
30+
BlazeCampaignTargetSelectionScreen(viewModel)
31+
}
32+
}
33+
34+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
35+
super.onViewCreated(view, savedInstanceState)
36+
setupObservers()
37+
}
38+
39+
private fun setupObservers() {
40+
viewModel.event.observe(viewLifecycleOwner) { event ->
41+
when (event) {
42+
is MultiLiveEvent.Event.Exit -> findNavController().popBackStack()
43+
is ExitWithResult<*> -> navigateBackWithResult(BLAZE_TARGET_LOCATION_RESULT, event.data)
44+
}
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)