Skip to content

Commit

Permalink
Merge pull request #10680 from woocommerce/issue/10630-location-selec…
Browse files Browse the repository at this point in the history
…tion

Blaze: Target location selection
  • Loading branch information
0nko authored Feb 6, 2024
2 parents 7d59059 + 356d53b commit e453707
Show file tree
Hide file tree
Showing 15 changed files with 778 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,47 @@ package com.woocommerce.android.extensions
import kotlinx.coroutines.flow.Flow

@Suppress("LongParameterList")
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
inline fun <T1, T2, T3, T4, T5, T6, R> combine(
flow: Flow<T1>,
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
flow5: Flow<T5>,
flow6: Flow<T6>,
flow7: Flow<T7>,
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
): Flow<R> {
return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
return kotlinx.coroutines.flow.combine(
flow,
flow2,
flow3,
flow4,
flow5,
flow6,
) { args: Array<*> ->
@Suppress("UNCHECKED_CAST", "MagicNumber")
transform(
args[0] as T1,
args[1] as T2,
args[2] as T3,
args[3] as T4,
args[4] as T5,
args[5] as T6,
args[6] as T7,
args[5] as T6
)
}
}

@Suppress("LongParameterList")
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
flow: Flow<T1>,
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
flow5: Flow<T5>,
flow6: Flow<T6>,
flow7: Flow<T7>,
flow8: Flow<T8>,
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
): Flow<R> {
return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { args: Array<*> ->
return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { args: Array<*> ->
@Suppress("UNCHECKED_CAST", "MagicNumber")
transform(
args[0] as T1,
Expand All @@ -49,7 +53,6 @@ inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
args[4] as T5,
args[5] as T6,
args[6] as T7,
args[7] as T8,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,16 @@ class BlazeRepository @Inject constructor(
fun observeDevices() = blazeCampaignsStore.observeBlazeTargetingDevices()
.map { it.map { device -> Device(device.id, device.name) } }

suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingTopics()
suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingDevices()

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

suspend fun fetchInterests() = blazeCampaignsStore.fetchBlazeTargetingTopics()

suspend fun fetchLocations(query: String) = blazeCampaignsStore.fetchBlazeTargetingLocations(query).model
?.map { location -> Location(location.id, location.name, location.parent?.name, location.type) }

suspend fun getMostRecentCampaign() = blazeCampaignsStore.getMostRecentBlazeCampaign(selectedSite.get())

suspend fun getAdSuggestions(productId: Long): List<AiSuggestionForAd>? {
Expand Down Expand Up @@ -91,28 +94,30 @@ class BlazeRepository @Inject constructor(
val durationInDays: Int,
val startDate: Date,
) : Parcelable

@Parcelize
data class Location(
val id: String,
val name: String,
) : Parcelable

@Parcelize
data class Language(
val code: String,
val name: String,
) : Parcelable

@Parcelize
data class Device(
val id: String,
val name: String,
) : Parcelable

@Parcelize
data class Interest(
val id: String,
val description: String,
) : Parcelable
}

@Parcelize
data class Location(
val id: Long,
val name: String,
val parent: String? = null,
val type: String? = null
) : Parcelable

@Parcelize
data class Language(
val code: String,
val name: String,
) : Parcelable

@Parcelize
data class Device(
val id: String,
val name: String,
) : Parcelable

@Parcelize
data class Interest(
val id: String,
val description: String,
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdF
import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.EditAdResult
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToBudgetScreen
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToEditAdScreen
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToTargetLocationSelectionScreen
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.NavigateToTargetSelectionScreen
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetLocationSelectionFragment
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetLocationSelectionViewModel.TargetLocationResult
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionFragment
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionViewModel.TargetSelectionResult
import com.woocommerce.android.ui.compose.composeView
Expand Down Expand Up @@ -64,6 +67,12 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() {
event.selectedIds.toTypedArray()
)
)
is NavigateToTargetLocationSelectionScreen -> findNavController().navigateSafely(
BlazeCampaignCreationPreviewFragmentDirections
.actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignTargetLocationSelectionFragment(
event.locations.toTypedArray()
)
)
}
}
}
Expand All @@ -75,5 +84,8 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() {
handleResult<TargetSelectionResult>(BlazeCampaignTargetSelectionFragment.BLAZE_TARGET_SELECTION_RESULT) {
viewModel.onTargetSelectionUpdated(it.targetType, it.selectedIds)
}
handleResult<TargetLocationResult>(BlazeCampaignTargetLocationSelectionFragment.BLAZE_TARGET_LOCATION_RESULT) {
viewModel.onTargetLocationsUpdated(it.locations)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import com.woocommerce.android.extensions.formatToMMMdd
import com.woocommerce.android.ui.blaze.BlazeRepository
import com.woocommerce.android.ui.blaze.BlazeRepository.Budget
import com.woocommerce.android.ui.blaze.BlazeRepository.CampaignPreview
import com.woocommerce.android.ui.blaze.BlazeRepository.Device
import com.woocommerce.android.ui.blaze.BlazeRepository.Interest
import com.woocommerce.android.ui.blaze.BlazeRepository.Language
import com.woocommerce.android.ui.blaze.BlazeRepository.Location
import com.woocommerce.android.ui.blaze.Device
import com.woocommerce.android.ui.blaze.Interest
import com.woocommerce.android.ui.blaze.Language
import com.woocommerce.android.ui.blaze.Location
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.AdDetails
import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading
import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType
Expand All @@ -27,6 +27,7 @@ import com.woocommerce.android.viewmodel.ScopedViewModel
import com.woocommerce.android.viewmodel.getStateFlow
import com.woocommerce.android.viewmodel.navArgs
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
Expand All @@ -49,40 +50,57 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
private val languages = blazeRepository.observeLanguages()
private val devices = blazeRepository.observeDevices()
private val interests = blazeRepository.observeInterests()
private val selectedLanguages = savedStateHandle.getStateFlow<List<String>>(

private val selectedLanguageCodes = savedStateHandle.getStateFlow<List<String>>(
scope = viewModelScope,
initialValue = emptyList(),
key = "selectedLanguages"
)
private val selectedDevices = savedStateHandle.getStateFlow<List<String>>(

private val selectedLanguages = combine(languages, selectedLanguageCodes) { languages, selectedCodes ->
languages.filter { it.code in selectedCodes }
}

private val selectedDeviceIds = savedStateHandle.getStateFlow<List<String>>(
scope = viewModelScope,
initialValue = emptyList(),
key = "selectedDevices"
)
private val selectedInterests = savedStateHandle.getStateFlow<List<String>>(

private val selectedDevices = combine(devices, selectedDeviceIds) { devices, selectedIds ->
devices.filter { it.id in selectedIds }
}
private val selectedInterestIds = savedStateHandle.getStateFlow<List<String>>(
scope = viewModelScope,
initialValue = emptyList(),
key = "selectedInterests"
)

private val selectedInterests = combine(interests, selectedInterestIds) { interests, selectedIds ->
interests.filter { it.id in selectedIds }
}

private val selectedLocations = savedStateHandle.getStateFlow<List<Location>>(
scope = viewModelScope,
initialValue = emptyList()
)

val viewState = combine(
adDetails,
budget,
languages,
devices,
interests,
selectedLanguages,
selectedDevices,
selectedInterests
) { adDetails, budget, languages, devices, interests, selectedLanguages, selectedDevices, selectedInterests ->
selectedInterests,
selectedLocations
) { ad, budget, selectedLanguages, selectedDevices, selectedInterests, selectedLocations ->
CampaignPreviewUiState(
adDetails = adDetails,
adDetails = ad,
campaignDetails = campaign.toCampaignDetailsUi(
budget,
languages.filter { it.code in selectedLanguages },
devices.filter { it.id in selectedDevices },
interests.filter { it.id in selectedInterests },
emptyList()
selectedLanguages,
selectedDevices,
selectedInterests,
selectedLocations
)
)
}.asLiveData()
Expand Down Expand Up @@ -122,14 +140,18 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
fun onTargetSelectionUpdated(targetType: BlazeTargetType, selectedIds: List<String>) {
launch {
when (targetType) {
LANGUAGE -> selectedLanguages.update { selectedIds }
DEVICE -> selectedDevices.update { selectedIds }
INTEREST -> selectedInterests.update { selectedIds }
LANGUAGE -> selectedLanguageCodes.update { selectedIds }
DEVICE -> selectedDeviceIds.update { selectedIds }
INTEREST -> selectedInterestIds.update { selectedIds }
else -> Unit
}
}
}

fun onTargetLocationsUpdated(locations: List<Location>) {
selectedLocations.update { locations }
}

private fun loadData() {
launch {
blazeRepository.fetchLanguages()
Expand Down Expand Up @@ -182,7 +204,9 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location),
displayValue = locations.joinToString { it.name }
.ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) },
onItemSelected = { /* TODO Add location selection */ },
onItemSelected = {
triggerEvent(NavigateToTargetLocationSelectionScreen(locations))
},
),
CampaignDetailItemUi(
displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_interests),
Expand Down Expand Up @@ -261,8 +285,13 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
)

object NavigateToBudgetScreen : MultiLiveEvent.Event()

data class NavigateToTargetSelectionScreen(
val targetType: BlazeTargetType,
val selectedIds: List<String>
) : MultiLiveEvent.Event()

data class NavigateToTargetLocationSelectionScreen(
val locations: List<Location>
) : MultiLiveEvent.Event()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.woocommerce.android.ui.blaze.creation.targets

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.woocommerce.android.extensions.navigateBackWithResult
import com.woocommerce.android.ui.base.BaseFragment
import com.woocommerce.android.ui.compose.composeView
import com.woocommerce.android.ui.main.AppBarStatus
import com.woocommerce.android.viewmodel.MultiLiveEvent
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class BlazeCampaignTargetLocationSelectionFragment : BaseFragment() {
companion object {
const val BLAZE_TARGET_LOCATION_RESULT = "blaze_target_location_result"
}

override val activityAppBarStatus: AppBarStatus
get() = AppBarStatus.Hidden

val viewModel: BlazeCampaignTargetLocationSelectionViewModel by viewModels()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return composeView {
BlazeCampaignTargetSelectionScreen(viewModel)
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupObservers()
}

private fun setupObservers() {
viewModel.event.observe(viewLifecycleOwner) { event ->
when (event) {
is MultiLiveEvent.Event.Exit -> findNavController().popBackStack()
is ExitWithResult<*> -> navigateBackWithResult(BLAZE_TARGET_LOCATION_RESULT, event.data)
}
}
}
}
Loading

0 comments on commit e453707

Please sign in to comment.