Skip to content

Commit

Permalink
Merge pull request #10641 from woocommerce/issue/10589-language-selec…
Browse files Browse the repository at this point in the history
…tion

Blaze: Ad target languages
  • Loading branch information
0nko authored Jan 30, 2024
2 parents eb5d1c9 + ec280ac commit 91fce81
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.os.Parcelable
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.products.ProductDetailRepository
import com.woocommerce.android.util.TimezoneProvider
import kotlinx.coroutines.flow.map
import kotlinx.parcelize.Parcelize
import org.wordpress.android.fluxc.persistence.blaze.BlazeCampaignsDao.BlazeAdSuggestionEntity
import org.wordpress.android.fluxc.store.blaze.BlazeCampaignsStore
Expand All @@ -26,6 +27,11 @@ class BlazeRepository @Inject constructor(
const val ONE_DAY_IN_MILLIS = 1000 * 60 * 60 * 24
}

fun observeLanguages() = blazeCampaignsStore.observeBlazeTargetingLanguages()
.map { it.map { language -> Language(language.id, language.name) } }

suspend fun fetchLanguages() = blazeCampaignsStore.fetchBlazeTargetingLanguages()

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

suspend fun getAdSuggestions(productId: Long): List<AiSuggestionForAd>? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ 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.NavigateToTargetSelectionScreen
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
import com.woocommerce.android.ui.main.AppBarStatus
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
Expand Down Expand Up @@ -54,6 +57,13 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() {
event.campaignImageUrl
)
)
is NavigateToTargetSelectionScreen -> findNavController().navigateSafely(
BlazeCampaignCreationPreviewFragmentDirections
.actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignTargetSelectionFragment(
event.targetType,
event.selectedIds.toTypedArray()
)
)
}
}
}
Expand All @@ -62,5 +72,8 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() {
handleResult<EditAdResult>(BlazeCampaignCreationEditAdFragment.EDIT_AD_RESULT) {
viewModel.onAdUpdated(it.tagline, it.description, it.campaignImageUrl)
}
handleResult<TargetSelectionResult>(BlazeCampaignTargetSelectionFragment.BLAZE_TARGET_SELECTION_RESULT) {
viewModel.onTargetSelectionUpdated(it.targetType, it.selectedIds)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.woocommerce.android.R.string
import com.woocommerce.android.extensions.combine
import com.woocommerce.android.extensions.formatToMMMdd
import com.woocommerce.android.ui.blaze.BlazeRepository
import com.woocommerce.android.ui.blaze.BlazeRepository.Budget
Expand All @@ -16,13 +15,16 @@ import com.woocommerce.android.ui.blaze.BlazeRepository.Language
import com.woocommerce.android.ui.blaze.BlazeRepository.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
import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.LANGUAGE
import com.woocommerce.android.util.CurrencyFormatter
import com.woocommerce.android.viewmodel.MultiLiveEvent
import com.woocommerce.android.viewmodel.ResourceProvider
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 @@ -41,27 +43,29 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(

private val adDetails = savedStateHandle.getStateFlow<AdDetailsUi>(viewModelScope, Loading)
private val budget = savedStateHandle.getStateFlow(viewModelScope, getDefaultBudget())
private val selectedLanguages = savedStateHandle.getStateFlow<List<Language>>(viewModelScope, emptyList())
private val selectedDevices = savedStateHandle.getStateFlow<List<Device>>(viewModelScope, emptyList())
private val selectedInterests = savedStateHandle.getStateFlow<List<Interest>>(viewModelScope, emptyList())
private val selectedLocations = savedStateHandle.getStateFlow<List<Location>>(viewModelScope, emptyList())
private val languages = blazeRepository.observeLanguages()
private val selectedLanguages = savedStateHandle.getStateFlow<List<String>>(viewModelScope, emptyList())

val viewState = combine(
adDetails,
budget,
selectedLanguages,
selectedDevices,
selectedInterests,
selectedLocations
) { adDetails, budget, languages, devices, interests, locations ->
languages,
selectedLanguages
) { adDetails, budget, languages, selectedLanguages ->
CampaignPreviewUiState(
adDetails = adDetails,
campaignDetails = campaign.toCampaignDetailsUi(budget, languages, devices, locations, interests)
campaignDetails = campaign.toCampaignDetailsUi(
budget,
languages.filter { it.code in selectedLanguages },
emptyList(),
emptyList(),
emptyList()
)
)
}.asLiveData()

init {
loadSuggestions()
loadData()
}

fun onBackPressed() {
Expand Down Expand Up @@ -92,8 +96,18 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
}
}

private fun loadSuggestions() {
fun onTargetSelectionUpdated(targetType: BlazeTargetType, selectedIds: List<String>) {
launch {
when (targetType) {
LANGUAGE -> selectedLanguages.update { selectedIds }
else -> Unit
}
}
}

private fun loadData() {
launch {
blazeRepository.fetchLanguages()
blazeRepository.getAdSuggestions(navArgs.productId).let { suggestions ->
adDetails.update {
AdDetails(
Expand All @@ -111,8 +125,8 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
budget: Budget,
languages: List<Language>,
devices: List<Device>,
locations: List<Location>,
interests: List<Interest>
interests: List<Interest>,
locations: List<Location>
) = CampaignDetailsUi(
budget = CampaignDetailItemUi(
displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_budget),
Expand All @@ -124,13 +138,15 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_language),
displayValue = languages.joinToString { it.name }
.ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) },
onItemSelected = { /* TODO Add language selection */ },
onItemSelected = {
triggerEvent(NavigateToTargetSelectionScreen(LANGUAGE, languages.map { it.code }))
},
),
CampaignDetailItemUi(
displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_devices),
displayValue = devices.joinToString { it.name }
.ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) },
onItemSelected = { /* TODO Add devices selection */ },
onItemSelected = { /* TODO Add device selection */ },
),
CampaignDetailItemUi(
displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location),
Expand Down Expand Up @@ -213,4 +229,8 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor(
)

object NavigateToBudgetScreen : MultiLiveEvent.Event()
data class NavigateToTargetSelectionScreen(
val targetType: BlazeTargetType,
val selectedIds: List<String>
) : 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 BlazeCampaignTargetSelectionFragment : BaseFragment() {
companion object {
const val BLAZE_TARGET_SELECTION_RESULT = "blaze_target_selection_result"
}

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

val viewModel: BlazeCampaignTargetSelectionViewModel 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_SELECTION_RESULT, event.data)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.woocommerce.android.ui.blaze.creation.targets

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.icons.Icons.Filled
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.woocommerce.android.R.string
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionViewModel.TargetItem
import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSelectionViewModel.ViewState
import com.woocommerce.android.ui.compose.component.MultiSelectAllItemsButton
import com.woocommerce.android.ui.compose.component.MultiSelectList
import com.woocommerce.android.ui.compose.component.Toolbar
import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews

@Composable
fun BlazeCampaignTargetSelectionScreen(viewModel: BlazeCampaignTargetSelectionViewModel) {
viewModel.viewState.observeAsState().value?.let { state ->
TargetSelectionScreen(
state = state,
onBackPressed = viewModel::onBackPressed,
onSaveTapped = viewModel::onSaveTapped,
onItemTapped = viewModel::onItemTapped,
onAllButtonTapped = viewModel::onAllButtonTapped
)
}
}

@Composable
private fun TargetSelectionScreen(
state: ViewState,
onBackPressed: () -> Unit,
onSaveTapped: () -> Unit,
onItemTapped: (TargetItem) -> Unit,
onAllButtonTapped: () -> Unit
) {
Scaffold(
topBar = {
Toolbar(
title = state.title,
onNavigationButtonClick = onBackPressed,
navigationIcon = Filled.ArrowBack,
actionButtonText = stringResource(id = string.save).uppercase(),
onActionButtonClick = onSaveTapped
)
},
modifier = Modifier.background(MaterialTheme.colors.surface)
) { paddingValues ->
MultiSelectList(
items = state.items,
selectedItems = state.selectedItems,
itemFormatter = { value },
onItemToggled = onItemTapped,
allItemsButton = MultiSelectAllItemsButton(
text = stringResource(id = string.blaze_campaign_preview_target_default_value),
onClicked = onAllButtonTapped
),
modifier = Modifier
.background(MaterialTheme.colors.surface)
.padding(paddingValues)
)
}
}

@LightDarkThemePreviews
@Composable
fun PreviewTargetSelectionScreen() {
TargetSelectionScreen(
state = ViewState(
items = listOf(
TargetItem("1", "Item 1"),
TargetItem("2", "Item 2"),
TargetItem("3", "Item 3"),
TargetItem("4", "Item 4"),
TargetItem("5", "Item 5"),
TargetItem("6", "Item 6"),
TargetItem("7", "Item 7"),
TargetItem("8", "Item 8"),
TargetItem("9", "Item 9")
),
selectedItems = listOf(
TargetItem("4", "Item 4"),
TargetItem("5", "Item 5"),
TargetItem("8", "Item 8"),
TargetItem("9", "Item 9")
),
title = "Title"
),
onBackPressed = { /*TODO*/ },
onSaveTapped = { /*TODO*/ },
onItemTapped = {},
onAllButtonTapped = {}
)
}
Loading

0 comments on commit 91fce81

Please sign in to comment.