From d5dcb2a36aac7664c322c81f5e20d0a769eb2fd6 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:03:36 +0100 Subject: [PATCH 1/5] Add device target selection --- .../woocommerce/android/ui/blaze/BlazeRepository.kt | 5 +++++ .../preview/BlazeCampaignCreationPreviewViewModel.kt | 6 +++++- .../targets/BlazeCampaignTargetSelectionViewModel.kt | 12 +++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt index feef8c1de85..79dde8216a2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt @@ -32,6 +32,11 @@ class BlazeRepository @Inject constructor( suspend fun fetchLanguages() = blazeCampaignsStore.fetchBlazeTargetingLanguages() + fun observeDevices() = blazeCampaignsStore.observeBlazeTargetingDevices() + .map { it.map { device -> Device(device.id, device.name) } } + + suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingDevices() + suspend fun getMostRecentCampaign() = blazeCampaignsStore.getMostRecentBlazeCampaign(selectedSite.get()) suspend fun getAdSuggestions(productId: Long): List? { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt index fb23ea12acb..82fa8e27062 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt @@ -100,6 +100,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( launch { when (targetType) { LANGUAGE -> selectedLanguages.update { selectedIds } + DEVICE -> selectedDevices.update { selectedIds } else -> Unit } } @@ -108,6 +109,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private fun loadData() { launch { blazeRepository.fetchLanguages() + blazeRepository.fetchDevices() blazeRepository.getAdSuggestions(navArgs.productId).let { suggestions -> adDetails.update { AdDetails( @@ -146,7 +148,9 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( 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 device selection */ }, + onItemSelected = { + triggerEvent(NavigateToTargetSelectionScreen(DEVICE, devices.map { it.id })) + }, ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt index 139ae0e1a0a..fc57dfd8778 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -15,7 +15,6 @@ import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.parcelize.Parcelize @@ -38,7 +37,14 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( ) } } - else -> flowOf(emptyList()) + else -> blazeRepository.observeDevices().map { devices -> + devices.map { device -> + TargetItem( + id = device.id, + value = device.name + ) + } + } } private val selectedIds = savedStateHandle.getStateFlow(viewModelScope, navArgs.selectedIds.toSet()) @@ -49,7 +55,7 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( selectedItems = selectedIds.map { id -> items.first { it.id == id } }, title = when (navArgs.targetType) { BlazeTargetType.LANGUAGE -> resourceProvider.getString(R.string.blaze_campaign_preview_details_language) - else -> "" + else -> resourceProvider.getString(R.string.blaze_campaign_preview_details_devices) } ) }.asLiveData() From 348d6543856f213cfc91370bc94ac6eeb37d135d Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 29 Jan 2024 13:04:47 +0100 Subject: [PATCH 2/5] Combine the state of selected languages and devices --- .../BlazeCampaignCreationPreviewViewModel.kt | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt index 82fa8e27062..f3664005a75 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt @@ -5,6 +5,7 @@ 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 @@ -16,6 +17,7 @@ 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.DEVICE import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.LANGUAGE import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -24,7 +26,6 @@ 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 @@ -43,21 +44,34 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val adDetails = savedStateHandle.getStateFlow(viewModelScope, Loading) private val budget = savedStateHandle.getStateFlow(viewModelScope, getDefaultBudget()) + private val languages = blazeRepository.observeLanguages() - private val selectedLanguages = savedStateHandle.getStateFlow>(viewModelScope, emptyList()) + private val devices = blazeRepository.observeDevices() + private val selectedLanguages = savedStateHandle.getStateFlow>( + scope = viewModelScope, + initialValue = emptyList(), + key = "selectedLanguages" + ) + private val selectedDevices = savedStateHandle.getStateFlow>( + scope = viewModelScope, + initialValue = emptyList(), + key = "selectedDevices" + ) val viewState = combine( adDetails, budget, languages, - selectedLanguages - ) { adDetails, budget, languages, selectedLanguages -> + devices, + selectedLanguages, + selectedDevices + ) { adDetails, budget, languages, devices, selectedLanguages, selectedDevices -> CampaignPreviewUiState( adDetails = adDetails, campaignDetails = campaign.toCampaignDetailsUi( budget, languages.filter { it.code in selectedLanguages }, - emptyList(), + devices.filter { it.id in selectedDevices }, emptyList(), emptyList() ) From 1ff994617834f8b8e277912c4d0af63276cb2d14 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 30 Jan 2024 10:23:52 +0100 Subject: [PATCH 3/5] Disable the Save buttons if no options selected --- .../targets/BlazeCampaignTargetSelectionScreen.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt index 6cc1dfaaa46..4a6e525e919 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionScreen.kt @@ -4,6 +4,8 @@ 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.Text +import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons.Filled import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.runtime.Composable @@ -45,8 +47,11 @@ private fun TargetSelectionScreen( title = state.title, onNavigationButtonClick = onBackPressed, navigationIcon = Filled.ArrowBack, - actionButtonText = stringResource(id = string.save).uppercase(), - onActionButtonClick = onSaveTapped + actions = { + TextButton(onClick = onSaveTapped, enabled = state.selectedItems.isNotEmpty()) { + Text(stringResource(id = string.save).uppercase()) + } + } ) }, modifier = Modifier.background(MaterialTheme.colors.surface) From 8fd1bac3ea376cf5d8b7f0e80b87e2717607f5fb Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 30 Jan 2024 10:31:19 +0100 Subject: [PATCH 4/5] Make the All button a toggle between "all" and "none" selection --- .../targets/BlazeCampaignTargetSelectionViewModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt index fc57dfd8778..f4dd550af6c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/targets/BlazeCampaignTargetSelectionViewModel.kt @@ -71,7 +71,13 @@ class BlazeCampaignTargetSelectionViewModel @Inject constructor( } fun onAllButtonTapped() { - selectedIds.update { emptySet() } + selectedIds.update { + if (it.size == viewState.value?.items?.size) { + emptySet() + } else { + viewState.value?.items?.map { item -> item.id }?.toSet() ?: emptySet() + } + } } fun onBackPressed() { From 68863ac6d6ca2003692aa709b923b80d38925db4 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 30 Jan 2024 10:32:21 +0100 Subject: [PATCH 5/5] Show the All button as selected when all items are selected --- .../woocommerce/android/ui/compose/component/MultiSelectList.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/MultiSelectList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/MultiSelectList.kt index 3f7ad2bfc28..faafac4fe98 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/MultiSelectList.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/compose/component/MultiSelectList.kt @@ -44,7 +44,7 @@ fun MultiSelectList( allItemsButton?.let { MultiSelectItem( item = it.text, - isSelected = selectedItems.isEmpty(), + isSelected = selectedItems.size == items.size, onItemToggled = it.onClicked, modifier = Modifier.fillMaxWidth() )