From 4a4d71eb78498b6c7c4597d55ba1d9015f94a146 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 6 Feb 2024 15:46:31 +0100 Subject: [PATCH 01/12] Add logic to update budget slider values correctly When updating campaign duration we also need to update the budget slider range as well as the total budget value --- .../android/ui/blaze/BlazeRepository.kt | 1 - .../budget/BlazeCampaignBudgetScreen.kt | 5 ++- .../budget/BlazeCampaignBudgetViewModel.kt | 37 +++++++++++++------ .../BlazeCampaignCreationPreviewViewModel.kt | 6 ++- 4 files changed, 32 insertions(+), 17 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 b6e75e1087d..249e9de01a0 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 @@ -20,7 +20,6 @@ class BlazeRepository @Inject constructor( companion object { const val BLAZE_DEFAULT_CURRENCY_CODE = "USD" // For now only USD are supported const val DEFAULT_CAMPAIGN_DURATION = 7 // Days - const val DEFAULT_CAMPAIGN_TOTAL_BUDGET = 35F // USD const val CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT = 5F // USD const val CAMPAIGN_MAXIMUM_DAILY_SPEND_LIMIT = 50F // USD const val CAMPAIGN_MAX_DURATION = 28 // Days diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index e111aaa1880..a88c5dc92b7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -53,7 +53,7 @@ fun CampaignBudgetScreen(viewModel: BlazeCampaignBudgetViewModel) { onBackPressed = viewModel::onBackPressed, onEditDurationTapped = viewModel::onEditDurationTapped, onImpressionsInfoTapped = viewModel::onImpressionsInfoTapped, - onBudgetUpdated = viewModel::onTotalBudgetUpdated, + onBudgetUpdated = viewModel::onBudgetUpdated, onCampaignDurationUpdated = viewModel::onCampaignDurationUpdated, ) } @@ -181,7 +181,7 @@ private fun EditBudgetSection( ) Slider( modifier = Modifier.padding(top = 8.dp, bottom = 8.dp), - value = state.totalBudget, + value = state.sliderValue, valueRange = state.budgetRange, onValueChange = { onBudgetUpdated(it) }, colors = SliderDefaults.colors( @@ -347,6 +347,7 @@ private fun CampaignBudgetScreenPreview() { CampaignBudgetScreen( state = BlazeCampaignBudgetViewModel.BudgetUiState( totalBudget = 35f, + sliderValue = 35f, spentBudget = 0f, budgetRange = 35f..350f, currencyCode = "USD", diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt index 7f7fc0ea9b7..370acd55c7d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt @@ -8,7 +8,6 @@ import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAXIM import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAX_DURATION import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.DEFAULT_CAMPAIGN_DURATION -import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.DEFAULT_CAMPAIGN_TOTAL_BUDGET import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.ScopedViewModel @@ -24,10 +23,13 @@ class BlazeCampaignBudgetViewModel @Inject constructor( private val _viewState = MutableLiveData( BudgetUiState( currencyCode = BLAZE_DEFAULT_CURRENCY_CODE, - totalBudget = DEFAULT_CAMPAIGN_TOTAL_BUDGET, + totalBudget = CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT * DEFAULT_CAMPAIGN_DURATION, + sliderValue = CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT * DEFAULT_CAMPAIGN_DURATION, spentBudget = 0f, budgetRange = getBudgetRange(DEFAULT_CAMPAIGN_DURATION), - dailySpending = getDailySpending(DEFAULT_CAMPAIGN_TOTAL_BUDGET, DEFAULT_CAMPAIGN_DURATION), + dailySpending = formatDailySpend( + dailySpend = (CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT * DEFAULT_CAMPAIGN_DURATION) / DEFAULT_CAMPAIGN_DURATION + ), durationInDays = DEFAULT_CAMPAIGN_DURATION, durationRangeDays = getDurationRange(), startDateMmmDdYyyy = Date().formatToMMMddYYYY(), @@ -58,32 +60,43 @@ class BlazeCampaignBudgetViewModel @Inject constructor( ) } - fun onTotalBudgetUpdated(totalBudget: Float) { + fun onBudgetUpdated(sliderValue: Float) { + if (sliderValue.toInt().mod(viewState.value?.durationInDays!!) == 0) { + val dailySpending = sliderValue / viewState.value?.durationInDays!! + _viewState.value = _viewState.value?.copy( + totalBudget = sliderValue, + dailySpending = formatDailySpend(dailySpending) + ) + } _viewState.value = _viewState.value?.copy( - totalBudget = totalBudget, - dailySpending = getDailySpending(totalBudget, viewState.value?.durationInDays!!) + sliderValue = sliderValue, ) } fun onCampaignDurationUpdated(duration: Int) { + val currentDailyExpend = viewState.value?.totalBudget!! / viewState.value?.durationInDays!! + val newTotalBudget = duration * currentDailyExpend _viewState.value = _viewState.value?.copy( - budgetRange = getBudgetRange(duration), durationInDays = duration, - dailySpending = getDailySpending(viewState.value?.totalBudget!!, duration) + budgetRange = getBudgetRange(duration), + dailySpending = formatDailySpend(currentDailyExpend), + totalBudget = newTotalBudget, + sliderValue = newTotalBudget, ) } - private fun getDailySpending(totalBudget: Float, duration: Int) = - currencyFormatter.formatCurrency((totalBudget / duration).toBigDecimal(), BLAZE_DEFAULT_CURRENCY_CODE) + private fun formatDailySpend(dailySpend: Float) = + currencyFormatter.formatCurrency(dailySpend.toBigDecimal(), BLAZE_DEFAULT_CURRENCY_CODE) - private fun getBudgetRange(currentDuration: Int) = - currentDuration * CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT..currentDuration * CAMPAIGN_MAXIMUM_DAILY_SPEND_LIMIT + private fun getBudgetRange(duration: Int) = + duration * CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT..duration * CAMPAIGN_MAXIMUM_DAILY_SPEND_LIMIT private fun getDurationRange() = 1f..CAMPAIGN_MAX_DURATION.toFloat() data class BudgetUiState( val currencyCode: String, val totalBudget: Float, + val sliderValue: Float, val spentBudget: Float, val budgetRange: ClosedFloatingPointRange, val dailySpending: String, 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 ba679268669..8b256aad705 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 @@ -10,6 +10,8 @@ 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.Companion.CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT +import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.DEFAULT_CAMPAIGN_DURATION import com.woocommerce.android.ui.blaze.BlazeRepository.Device import com.woocommerce.android.ui.blaze.BlazeRepository.Interest import com.woocommerce.android.ui.blaze.BlazeRepository.Language @@ -215,10 +217,10 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( } private fun getDefaultBudget() = Budget( - totalBudget = BlazeRepository.DEFAULT_CAMPAIGN_TOTAL_BUDGET, + totalBudget = DEFAULT_CAMPAIGN_DURATION * CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT, spentBudget = 0f, currencyCode = BlazeRepository.BLAZE_DEFAULT_CURRENCY_CODE, - durationInDays = BlazeRepository.DEFAULT_CAMPAIGN_DURATION, + durationInDays = DEFAULT_CAMPAIGN_DURATION, startDate = Date().apply { time += BlazeRepository.ONE_DAY_IN_MILLIS }, // By default start tomorrow ) From 07c0bed4c6ebe6d04486fc83de7eb52d2176698d Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 6 Feb 2024 16:33:28 +0100 Subject: [PATCH 02/12] Show daily spend as integer as decimals should always be 0 --- .../ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt index 370acd55c7d..a682b6bbd7e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.viewmodel.ScopedViewModel import dagger.hilt.android.lifecycle.HiltViewModel import java.util.Date import javax.inject.Inject +import kotlin.math.roundToInt @HiltViewModel class BlazeCampaignBudgetViewModel @Inject constructor( @@ -86,7 +87,7 @@ class BlazeCampaignBudgetViewModel @Inject constructor( } private fun formatDailySpend(dailySpend: Float) = - currencyFormatter.formatCurrency(dailySpend.toBigDecimal(), BLAZE_DEFAULT_CURRENCY_CODE) + currencyFormatter.formatCurrency(dailySpend.roundToInt().toBigDecimal(), BLAZE_DEFAULT_CURRENCY_CODE) private fun getBudgetRange(duration: Int) = duration * CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT..duration * CAMPAIGN_MAXIMUM_DAILY_SPEND_LIMIT From 67863439282b07eda0d7a69e38fd9e5293e8a9a3 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 6 Feb 2024 23:52:52 +0100 Subject: [PATCH 03/12] Refactor constant names --- .../android/ui/blaze/BlazeRepository.kt | 4 ++-- .../budget/BlazeCampaignBudgetViewModel.kt | 18 +++++++++--------- .../BlazeCampaignCreationPreviewViewModel.kt | 4 ++-- 3 files changed, 13 insertions(+), 13 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 249e9de01a0..c295cdcc609 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 @@ -20,8 +20,8 @@ class BlazeRepository @Inject constructor( companion object { const val BLAZE_DEFAULT_CURRENCY_CODE = "USD" // For now only USD are supported const val DEFAULT_CAMPAIGN_DURATION = 7 // Days - const val CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT = 5F // USD - const val CAMPAIGN_MAXIMUM_DAILY_SPEND_LIMIT = 50F // USD + const val CAMPAIGN_MINIMUM_DAILY_SPEND = 5F // USD + const val CAMPAIGN_MAXIMUM_DAILY_SPEND = 50F // USD const val CAMPAIGN_MAX_DURATION = 28 // Days const val ONE_DAY_IN_MILLIS = 1000 * 60 * 60 * 24 } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt index a682b6bbd7e..bc2d0ba410f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt @@ -4,9 +4,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.extensions.formatToMMMddYYYY import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.BLAZE_DEFAULT_CURRENCY_CODE -import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAXIMUM_DAILY_SPEND_LIMIT +import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAXIMUM_DAILY_SPEND import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAX_DURATION -import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT +import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MINIMUM_DAILY_SPEND import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.DEFAULT_CAMPAIGN_DURATION import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit @@ -24,13 +24,11 @@ class BlazeCampaignBudgetViewModel @Inject constructor( private val _viewState = MutableLiveData( BudgetUiState( currencyCode = BLAZE_DEFAULT_CURRENCY_CODE, - totalBudget = CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT * DEFAULT_CAMPAIGN_DURATION, - sliderValue = CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT * DEFAULT_CAMPAIGN_DURATION, + totalBudget = CAMPAIGN_MINIMUM_DAILY_SPEND * DEFAULT_CAMPAIGN_DURATION, + sliderValue = CAMPAIGN_MINIMUM_DAILY_SPEND * DEFAULT_CAMPAIGN_DURATION, spentBudget = 0f, budgetRange = getBudgetRange(DEFAULT_CAMPAIGN_DURATION), - dailySpending = formatDailySpend( - dailySpend = (CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT * DEFAULT_CAMPAIGN_DURATION) / DEFAULT_CAMPAIGN_DURATION - ), + dailySpending = formatDailySpend(dailySpend = CAMPAIGN_MINIMUM_DAILY_SPEND), durationInDays = DEFAULT_CAMPAIGN_DURATION, durationRangeDays = getDurationRange(), startDateMmmDdYyyy = Date().formatToMMMddYYYY(), @@ -38,7 +36,8 @@ class BlazeCampaignBudgetViewModel @Inject constructor( isLoaded = false, impressionsMin = 0, impressionsMax = 0 - ) + ), + campaignDurationDates = "", ) ) val viewState = _viewState @@ -90,7 +89,7 @@ class BlazeCampaignBudgetViewModel @Inject constructor( currencyFormatter.formatCurrency(dailySpend.roundToInt().toBigDecimal(), BLAZE_DEFAULT_CURRENCY_CODE) private fun getBudgetRange(duration: Int) = - duration * CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT..duration * CAMPAIGN_MAXIMUM_DAILY_SPEND_LIMIT + duration * CAMPAIGN_MINIMUM_DAILY_SPEND..duration * CAMPAIGN_MAXIMUM_DAILY_SPEND private fun getDurationRange() = 1f..CAMPAIGN_MAX_DURATION.toFloat() @@ -107,6 +106,7 @@ class BlazeCampaignBudgetViewModel @Inject constructor( val forecast: ForecastUi, val showImpressionsBottomSheet: Boolean = false, val showCampaignDurationBottomSheet: Boolean = false, + val campaignDurationDates: String, ) data class ForecastUi( 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 8b256aad705..7ea01d38ccb 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 @@ -10,7 +10,7 @@ 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.Companion.CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT +import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MINIMUM_DAILY_SPEND import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.DEFAULT_CAMPAIGN_DURATION import com.woocommerce.android.ui.blaze.BlazeRepository.Device import com.woocommerce.android.ui.blaze.BlazeRepository.Interest @@ -217,7 +217,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( } private fun getDefaultBudget() = Budget( - totalBudget = DEFAULT_CAMPAIGN_DURATION * CAMPAIGN_MINIMUM_DAILY_SPEND_LIMIT, + totalBudget = DEFAULT_CAMPAIGN_DURATION * CAMPAIGN_MINIMUM_DAILY_SPEND, spentBudget = 0f, currencyCode = BlazeRepository.BLAZE_DEFAULT_CURRENCY_CODE, durationInDays = DEFAULT_CAMPAIGN_DURATION, From 91790b642dbf8cea22aad9fe33e6d7f37e953bea Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 7 Feb 2024 01:31:32 +0100 Subject: [PATCH 04/12] Update campaign duration dates section --- .../creation/budget/BlazeCampaignBudgetScreen.kt | 13 ++++++++----- .../creation/budget/BlazeCampaignBudgetViewModel.kt | 10 +++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index a88c5dc92b7..8df4605846f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -122,6 +122,7 @@ private fun CampaignBudgetScreen( modifier = Modifier.weight(1f) ) EditDurationSection( + campaignDurationDates = state.campaignDurationDates, onEditDurationTapped = { onEditDurationTapped() coroutineScope.launch { modalSheetState.show() } @@ -209,6 +210,7 @@ private fun EditBudgetSection( @Composable private fun EditDurationSection( + campaignDurationDates: String, onEditDurationTapped: () -> Unit ) { Column { @@ -230,7 +232,7 @@ private fun EditDurationSection( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Dec 13 – Dec 19, 2023", + text = campaignDurationDates, style = MaterialTheme.typography.subtitle2, fontWeight = FontWeight.SemiBold, ) @@ -346,20 +348,21 @@ private fun EditDurationBottomSheet( private fun CampaignBudgetScreenPreview() { CampaignBudgetScreen( state = BlazeCampaignBudgetViewModel.BudgetUiState( + currencyCode = "USD", totalBudget = 35f, sliderValue = 35f, spentBudget = 0f, budgetRange = 35f..350f, - currencyCode = "USD", - durationInDays = 7, dailySpending = "$5", - startDateMmmDdYyyy = "Dec 13, 2023", + durationInDays = 7, durationRangeDays = 1f..28f, + startDateMmmDdYyyy = "Dec 13, 2023", forecast = BlazeCampaignBudgetViewModel.ForecastUi( isLoaded = false, impressionsMin = 0, impressionsMax = 0 - ) + ), + campaignDurationDates = "Dec 13 - Dec 20, 2023", ), onBackPressed = {}, onEditDurationTapped = {}, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt index bc2d0ba410f..2e5e6f4fd79 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt @@ -2,12 +2,14 @@ package com.woocommerce.android.ui.blaze.creation.budget import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.extensions.formatToMMMdd import com.woocommerce.android.extensions.formatToMMMddYYYY import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.BLAZE_DEFAULT_CURRENCY_CODE import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAXIMUM_DAILY_SPEND import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAX_DURATION import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MINIMUM_DAILY_SPEND import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.DEFAULT_CAMPAIGN_DURATION +import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.ONE_DAY_IN_MILLIS import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.ScopedViewModel @@ -37,7 +39,7 @@ class BlazeCampaignBudgetViewModel @Inject constructor( impressionsMin = 0, impressionsMax = 0 ), - campaignDurationDates = "", + campaignDurationDates = getCampaignDurationDisplayDate(Date(), DEFAULT_CAMPAIGN_DURATION), ) ) val viewState = _viewState @@ -82,9 +84,15 @@ class BlazeCampaignBudgetViewModel @Inject constructor( dailySpending = formatDailySpend(currentDailyExpend), totalBudget = newTotalBudget, sliderValue = newTotalBudget, + campaignDurationDates = getCampaignDurationDisplayDate(Date(), duration) ) } + private fun getCampaignDurationDisplayDate(startDate: Date, duration: Int): String { + val endDate = Date(startDate.time + duration * ONE_DAY_IN_MILLIS) + return "${startDate.formatToMMMdd()} - ${endDate.formatToMMMddYYYY()}" + } + private fun formatDailySpend(dailySpend: Float) = currencyFormatter.formatCurrency(dailySpend.roundToInt().toBigDecimal(), BLAZE_DEFAULT_CURRENCY_CODE) From 2adf86af2409a36cb0ab7b6067bcdfe5f25bc015 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 7 Feb 2024 15:39:32 +0100 Subject: [PATCH 05/12] Enable updating campaign start date --- .../budget/BlazeCampaignBudgetScreen.kt | 39 +++++++++++++++---- .../budget/BlazeCampaignBudgetViewModel.kt | 17 +++++--- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index 8df4605846f..328d45cf1b0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -21,8 +21,12 @@ import androidx.compose.material.icons.Icons.Filled import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource @@ -37,13 +41,16 @@ import com.woocommerce.android.R import com.woocommerce.android.R.color import com.woocommerce.android.R.dimen import com.woocommerce.android.R.drawable +import com.woocommerce.android.extensions.formatToMMMddYYYY import com.woocommerce.android.ui.compose.component.BottomSheetHandle +import com.woocommerce.android.ui.compose.component.DatePickerDialog import com.woocommerce.android.ui.compose.component.Toolbar import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.component.WCModalBottomSheetLayout import com.woocommerce.android.ui.compose.component.WCTextButton import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews import kotlinx.coroutines.launch +import java.util.Date @Composable fun CampaignBudgetScreen(viewModel: BlazeCampaignBudgetViewModel) { @@ -55,6 +62,7 @@ fun CampaignBudgetScreen(viewModel: BlazeCampaignBudgetViewModel) { onImpressionsInfoTapped = viewModel::onImpressionsInfoTapped, onBudgetUpdated = viewModel::onBudgetUpdated, onCampaignDurationUpdated = viewModel::onCampaignDurationUpdated, + onStartDateChanged = viewModel::onStartDateChanged ) } } @@ -68,6 +76,7 @@ private fun CampaignBudgetScreen( onImpressionsInfoTapped: () -> Unit, onBudgetUpdated: (Float) -> Unit, onCampaignDurationUpdated: (Int) -> Unit, + onStartDateChanged: (Date) -> Unit, ) { val coroutineScope = rememberCoroutineScope() val modalSheetState = rememberModalBottomSheetState( @@ -98,9 +107,10 @@ private fun CampaignBudgetScreen( state.showCampaignDurationBottomSheet -> EditDurationBottomSheet( duration = state.durationInDays, - startDate = state.startDateMmmDdYyyy, + startDateMillis = state.campaignStartDateMillis, onDurationChanged = { onCampaignDurationUpdated(it.toInt()) }, durationRange = state.durationRangeDays, + onStartDateChanged = { onStartDateChanged(it) }, onApplyTapped = { coroutineScope.launch { modalSheetState.hide() } } ) } @@ -288,12 +298,25 @@ private fun ImpressionsInfoBottomSheet( @Composable private fun EditDurationBottomSheet( duration: Int, - startDate: String, durationRange: ClosedFloatingPointRange, + startDateMillis: Long, onDurationChanged: (Float) -> Unit, - onApplyTapped: () -> Unit, + onStartDateChanged: (Date) -> Unit, + onApplyTapped: () -> Unit = {}, modifier: Modifier = Modifier, ) { + var showDatePicker by remember { mutableStateOf(false) } + if (showDatePicker) { + DatePickerDialog( + currentDate = Date(startDateMillis), + onDateSelected = { + onStartDateChanged(it) + showDatePicker = false + }, + onDismissRequest = { showDatePicker = false } + ) + } + Column(modifier = modifier.padding(16.dp)) { Text( text = stringResource(id = R.string.blaze_campaign_budget_duration_bottom_sheet_title), @@ -329,7 +352,8 @@ private fun EditDurationBottomSheet( style = MaterialTheme.typography.body1, ) Text( - text = startDate, + modifier = Modifier.clickable { showDatePicker = !showDatePicker }, + text = Date(startDateMillis).formatToMMMddYYYY(), style = MaterialTheme.typography.body1, ) } @@ -356,12 +380,12 @@ private fun CampaignBudgetScreenPreview() { dailySpending = "$5", durationInDays = 7, durationRangeDays = 1f..28f, - startDateMmmDdYyyy = "Dec 13, 2023", forecast = BlazeCampaignBudgetViewModel.ForecastUi( isLoaded = false, impressionsMin = 0, impressionsMax = 0 ), + campaignStartDateMillis = Date().time, campaignDurationDates = "Dec 13 - Dec 20, 2023", ), onBackPressed = {}, @@ -369,6 +393,7 @@ private fun CampaignBudgetScreenPreview() { onImpressionsInfoTapped = {}, onBudgetUpdated = {}, onCampaignDurationUpdated = {}, + onStartDateChanged = {} ) } @@ -383,9 +408,9 @@ private fun CampaignImpressionsBottomSheetPreview() { private fun EditDurationBottomSheetPreview() { EditDurationBottomSheet( duration = 7, - startDate = "Dec 13, 2023", + startDateMillis = Date().time, durationRange = 1f..28f, onDurationChanged = {}, - onApplyTapped = {}, + onStartDateChanged = {}, ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt index 2e5e6f4fd79..22580b4e815 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt @@ -31,14 +31,14 @@ class BlazeCampaignBudgetViewModel @Inject constructor( spentBudget = 0f, budgetRange = getBudgetRange(DEFAULT_CAMPAIGN_DURATION), dailySpending = formatDailySpend(dailySpend = CAMPAIGN_MINIMUM_DAILY_SPEND), - durationInDays = DEFAULT_CAMPAIGN_DURATION, - durationRangeDays = getDurationRange(), - startDateMmmDdYyyy = Date().formatToMMMddYYYY(), forecast = ForecastUi( isLoaded = false, impressionsMin = 0, impressionsMax = 0 ), + durationInDays = DEFAULT_CAMPAIGN_DURATION, + durationRangeDays = getDurationRange(), + campaignStartDateMillis = Date().time, campaignDurationDates = getCampaignDurationDisplayDate(Date(), DEFAULT_CAMPAIGN_DURATION), ) ) @@ -88,6 +88,13 @@ class BlazeCampaignBudgetViewModel @Inject constructor( ) } + fun onStartDateChanged(newStartDate: Date) { + _viewState.value = _viewState.value?.copy( + campaignStartDateMillis = newStartDate.time, + campaignDurationDates = getCampaignDurationDisplayDate(newStartDate, viewState.value?.durationInDays!!) + ) + } + private fun getCampaignDurationDisplayDate(startDate: Date, duration: Int): String { val endDate = Date(startDate.time + duration * ONE_DAY_IN_MILLIS) return "${startDate.formatToMMMdd()} - ${endDate.formatToMMMddYYYY()}" @@ -108,12 +115,12 @@ class BlazeCampaignBudgetViewModel @Inject constructor( val spentBudget: Float, val budgetRange: ClosedFloatingPointRange, val dailySpending: String, + val forecast: ForecastUi, val durationInDays: Int, val durationRangeDays: ClosedFloatingPointRange, - val startDateMmmDdYyyy: String, - val forecast: ForecastUi, val showImpressionsBottomSheet: Boolean = false, val showCampaignDurationBottomSheet: Boolean = false, + val campaignStartDateMillis: Long, val campaignDurationDates: String, ) From 4be84aaf14316af0b7e5e35aac3f8a27ffc1a747 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 7 Feb 2024 15:46:24 +0100 Subject: [PATCH 06/12] Give edit campaign start date clickable feel --- .../blaze/creation/budget/BlazeCampaignBudgetScreen.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index 328d45cf1b0..6dff9571040 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon @@ -29,6 +30,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -345,6 +347,7 @@ private fun EditDurationBottomSheet( ) Row( modifier = Modifier.padding(top = 40.dp), + verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier.weight(1f), @@ -352,7 +355,11 @@ private fun EditDurationBottomSheet( style = MaterialTheme.typography.body1, ) Text( - modifier = Modifier.clickable { showDatePicker = !showDatePicker }, + modifier = Modifier + .clickable { showDatePicker = !showDatePicker } + .clip(RoundedCornerShape(4.dp)) + .background(colorResource(id = color.divider_color)) + .padding(8.dp), text = Date(startDateMillis).formatToMMMddYYYY(), style = MaterialTheme.typography.body1, ) From 386794487f683d2b932c992b8d96e90c050beab4 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 7 Feb 2024 15:49:01 +0100 Subject: [PATCH 07/12] Give edit campaign start date clickable feel --- .../ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index 6dff9571040..ccf8d0ca2bd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -366,7 +366,10 @@ private fun EditDurationBottomSheet( } WCColoredButton( modifier = Modifier - .padding(top = 40.dp) + .padding( + top = 30.dp, + bottom = 16.dp + ) .fillMaxWidth(), onClick = onApplyTapped, text = stringResource(id = R.string.blaze_campaign_budget_duration_bottom_sheet_apply_button) From 94d37959afc65befd2c5ffff2cf81cfe98ac5d3b Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 7 Feb 2024 16:30:38 +0100 Subject: [PATCH 08/12] Give edit campaign start date clickable feel --- .../budget/BlazeCampaignBudgetFragment.kt | 13 +++++++++++ .../budget/BlazeCampaignBudgetScreen.kt | 15 ++++++++----- .../budget/BlazeCampaignBudgetViewModel.kt | 22 +++++++++++++++++++ .../BlazeCampaignCreationPreviewFragment.kt | 7 ++++++ .../BlazeCampaignCreationPreviewViewModel.kt | 12 ++++++++++ .../nav_graph_blaze_campaign_creation.xml | 16 +++++++------- 6 files changed, 72 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetFragment.kt index 79f87a6ce89..b9d9f6850e2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetFragment.kt @@ -6,14 +6,21 @@ 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.blaze.creation.budget.BlazeCampaignBudgetViewModel.EditBudgetAndDurationResult 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 BlazeCampaignBudgetFragment : BaseFragment() { + companion object { + const val EDIT_BUDGET_AND_DURATION_RESULT = "edit_budget_and_duration_result" + } + override val activityAppBarStatus: AppBarStatus get() = AppBarStatus.Hidden @@ -34,6 +41,12 @@ class BlazeCampaignBudgetFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { is MultiLiveEvent.Event.Exit -> findNavController().popBackStack() + is ExitWithResult<*> -> { + navigateBackWithResult( + EDIT_BUDGET_AND_DURATION_RESULT, + event.data as EditBudgetAndDurationResult + ) + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index ccf8d0ca2bd..70df573c7a0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -64,7 +64,8 @@ fun CampaignBudgetScreen(viewModel: BlazeCampaignBudgetViewModel) { onImpressionsInfoTapped = viewModel::onImpressionsInfoTapped, onBudgetUpdated = viewModel::onBudgetUpdated, onCampaignDurationUpdated = viewModel::onCampaignDurationUpdated, - onStartDateChanged = viewModel::onStartDateChanged + onStartDateChanged = viewModel::onStartDateChanged, + onUpdateTapped = viewModel::onUpdateTapped ) } } @@ -79,6 +80,7 @@ private fun CampaignBudgetScreen( onBudgetUpdated: (Float) -> Unit, onCampaignDurationUpdated: (Int) -> Unit, onStartDateChanged: (Date) -> Unit, + onUpdateTapped: () -> Unit ) { val coroutineScope = rememberCoroutineScope() val modalSheetState = rememberModalBottomSheetState( @@ -138,7 +140,8 @@ private fun CampaignBudgetScreen( onEditDurationTapped = { onEditDurationTapped() coroutineScope.launch { modalSheetState.show() } - } + }, + onUpdateTapped = onUpdateTapped ) } } @@ -223,7 +226,8 @@ private fun EditBudgetSection( @Composable private fun EditDurationSection( campaignDurationDates: String, - onEditDurationTapped: () -> Unit + onEditDurationTapped: () -> Unit, + onUpdateTapped: () -> Unit, ) { Column { Divider() @@ -256,7 +260,7 @@ private fun EditDurationSection( } WCColoredButton( modifier = Modifier.fillMaxWidth(), - onClick = { /* TODO */ }, + onClick = onUpdateTapped, text = stringResource(id = R.string.blaze_campaign_budget_update_button) ) } @@ -403,7 +407,8 @@ private fun CampaignBudgetScreenPreview() { onImpressionsInfoTapped = {}, onBudgetUpdated = {}, onCampaignDurationUpdated = {}, - onStartDateChanged = {} + onStartDateChanged = {}, + onUpdateTapped = {} ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt index 22580b4e815..9a0808be20b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.blaze.creation.budget +import android.os.Parcelable import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.extensions.formatToMMMdd @@ -12,8 +13,10 @@ import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.DEFAULT_CAMPAI import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.ONE_DAY_IN_MILLIS import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit +import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import com.woocommerce.android.viewmodel.ScopedViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.parcelize.Parcelize import java.util.Date import javax.inject.Inject import kotlin.math.roundToInt @@ -48,6 +51,18 @@ class BlazeCampaignBudgetViewModel @Inject constructor( triggerEvent(Exit) } + fun onUpdateTapped() { + triggerEvent( + ExitWithResult( + EditBudgetAndDurationResult( + totalBudget = viewState.value?.totalBudget!!, + durationInDays = viewState.value?.durationInDays!!, + campaignStartDateMillis = viewState.value?.campaignStartDateMillis!! + ) + ) + ) + } + fun onEditDurationTapped() { _viewState.value = _viewState.value?.copy( showCampaignDurationBottomSheet = true, @@ -129,4 +144,11 @@ class BlazeCampaignBudgetViewModel @Inject constructor( val impressionsMin: Int, val impressionsMax: Int ) + + @Parcelize + data class EditBudgetAndDurationResult( + val totalBudget: Float, + val durationInDays: Int, + val campaignStartDateMillis: Long + ) : Parcelable } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt index b708dc42a64..c96a134ce51 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt @@ -11,6 +11,8 @@ import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdFragment import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.EditAdResult +import com.woocommerce.android.ui.blaze.creation.budget.BlazeCampaignBudgetFragment +import com.woocommerce.android.ui.blaze.creation.budget.BlazeCampaignBudgetViewModel.EditBudgetAndDurationResult 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 @@ -48,6 +50,7 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignBudgetFragment() ) + is NavigateToEditAdScreen -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignCreationEditAdFragment( @@ -57,6 +60,7 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { event.campaignImageUrl ) ) + is NavigateToTargetSelectionScreen -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignTargetSelectionFragment( @@ -72,6 +76,9 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { handleResult(BlazeCampaignCreationEditAdFragment.EDIT_AD_RESULT) { viewModel.onAdUpdated(it.tagline, it.description, it.campaignImageUrl) } + handleResult(BlazeCampaignBudgetFragment.EDIT_BUDGET_AND_DURATION_RESULT) { + viewModel.onBudgetAndDurationUpdated(it.totalBudget, it.durationInDays, it.campaignStartDateMillis) + } handleResult(BlazeCampaignTargetSelectionFragment.BLAZE_TARGET_SELECTION_RESULT) { viewModel.onTargetSelectionUpdated(it.targetType, it.selectedIds) } 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 7ea01d38ccb..4a2408a5a10 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 @@ -121,6 +121,18 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( } } + fun onBudgetAndDurationUpdated(totalBudget: Float, durationInDays: Int, campaignStartDateMillis: Long) { + budget.update { + Budget( + totalBudget = totalBudget, + spentBudget = 0f, + currencyCode = BlazeRepository.BLAZE_DEFAULT_CURRENCY_CODE, + durationInDays = durationInDays, + startDate = Date(campaignStartDateMillis) + ) + } + } + fun onTargetSelectionUpdated(targetType: BlazeTargetType, selectedIds: List) { launch { when (targetType) { diff --git a/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml b/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml index 22ed9b6623b..2263c4218f8 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml @@ -63,32 +63,32 @@ + android:label="BlazeCampaignCreationEditAdFragment"> + android:defaultValue="" + app:argType="string" /> + android:defaultValue="" + app:argType="string" /> + android:label="BlazeCampaignBudgetFragment"> + android:label="BlazeCampaignTargetSelectionFragment"> From ae039c287b5980343806f830e95a7d612262c99a Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 7 Feb 2024 17:06:17 +0100 Subject: [PATCH 09/12] Restore budget and duration state when opened from preview screen --- .../budget/BlazeCampaignBudgetScreen.kt | 7 ++- .../budget/BlazeCampaignBudgetViewModel.kt | 47 +++++++++++-------- .../BlazeCampaignCreationPreviewFragment.kt | 7 ++- .../BlazeCampaignCreationPreviewViewModel.kt | 19 +++++++- .../nav_graph_blaze_campaign_creation.xml | 15 +++++- 5 files changed, 67 insertions(+), 28 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index 70df573c7a0..8e74b08cd84 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -79,7 +79,7 @@ private fun CampaignBudgetScreen( onImpressionsInfoTapped: () -> Unit, onBudgetUpdated: (Float) -> Unit, onCampaignDurationUpdated: (Int) -> Unit, - onStartDateChanged: (Date) -> Unit, + onStartDateChanged: (Long) -> Unit, onUpdateTapped: () -> Unit ) { val coroutineScope = rememberCoroutineScope() @@ -307,7 +307,7 @@ private fun EditDurationBottomSheet( durationRange: ClosedFloatingPointRange, startDateMillis: Long, onDurationChanged: (Float) -> Unit, - onStartDateChanged: (Date) -> Unit, + onStartDateChanged: (Long) -> Unit, onApplyTapped: () -> Unit = {}, modifier: Modifier = Modifier, ) { @@ -316,7 +316,7 @@ private fun EditDurationBottomSheet( DatePickerDialog( currentDate = Date(startDateMillis), onDateSelected = { - onStartDateChanged(it) + onStartDateChanged(it.time) showDatePicker = false }, onDismissRequest = { showDatePicker = false } @@ -389,7 +389,6 @@ private fun CampaignBudgetScreenPreview() { currencyCode = "USD", totalBudget = 35f, sliderValue = 35f, - spentBudget = 0f, budgetRange = 35f..350f, dailySpending = "$5", durationInDays = 7, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt index 9a0808be20b..247baf23f3d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetViewModel.kt @@ -5,16 +5,15 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.extensions.formatToMMMdd import com.woocommerce.android.extensions.formatToMMMddYYYY -import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.BLAZE_DEFAULT_CURRENCY_CODE import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAXIMUM_DAILY_SPEND import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MAX_DURATION import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.CAMPAIGN_MINIMUM_DAILY_SPEND -import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.DEFAULT_CAMPAIGN_DURATION import com.woocommerce.android.ui.blaze.BlazeRepository.Companion.ONE_DAY_IN_MILLIS import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.parcelize.Parcelize import java.util.Date @@ -26,23 +25,26 @@ class BlazeCampaignBudgetViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val currencyFormatter: CurrencyFormatter ) : ScopedViewModel(savedStateHandle) { + private val navArgs: BlazeCampaignBudgetFragmentArgs by savedStateHandle.navArgs() + private val _viewState = MutableLiveData( BudgetUiState( - currencyCode = BLAZE_DEFAULT_CURRENCY_CODE, - totalBudget = CAMPAIGN_MINIMUM_DAILY_SPEND * DEFAULT_CAMPAIGN_DURATION, - sliderValue = CAMPAIGN_MINIMUM_DAILY_SPEND * DEFAULT_CAMPAIGN_DURATION, - spentBudget = 0f, - budgetRange = getBudgetRange(DEFAULT_CAMPAIGN_DURATION), - dailySpending = formatDailySpend(dailySpend = CAMPAIGN_MINIMUM_DAILY_SPEND), + currencyCode = navArgs.currencyCode, + totalBudget = navArgs.totalBudget, + sliderValue = navArgs.totalBudget, + budgetRange = getBudgetRange(navArgs.durationInDays), + dailySpending = formatDailySpend(dailySpend = navArgs.totalBudget / navArgs.durationInDays), forecast = ForecastUi( isLoaded = false, impressionsMin = 0, impressionsMax = 0 ), - durationInDays = DEFAULT_CAMPAIGN_DURATION, + durationInDays = navArgs.durationInDays, durationRangeDays = getDurationRange(), - campaignStartDateMillis = Date().time, - campaignDurationDates = getCampaignDurationDisplayDate(Date(), DEFAULT_CAMPAIGN_DURATION), + campaignStartDateMillis = navArgs.campaignStartDateMillis, + campaignDurationDates = getCampaignDurationDisplayDate( + navArgs.campaignStartDateMillis, navArgs.durationInDays + ), ) ) val viewState = _viewState @@ -99,24 +101,30 @@ class BlazeCampaignBudgetViewModel @Inject constructor( dailySpending = formatDailySpend(currentDailyExpend), totalBudget = newTotalBudget, sliderValue = newTotalBudget, - campaignDurationDates = getCampaignDurationDisplayDate(Date(), duration) + campaignDurationDates = getCampaignDurationDisplayDate( + viewState.value?.campaignStartDateMillis!!, + duration + ) ) } - fun onStartDateChanged(newStartDate: Date) { + fun onStartDateChanged(newStartDateMillis: Long) { _viewState.value = _viewState.value?.copy( - campaignStartDateMillis = newStartDate.time, - campaignDurationDates = getCampaignDurationDisplayDate(newStartDate, viewState.value?.durationInDays!!) + campaignStartDateMillis = newStartDateMillis, + campaignDurationDates = getCampaignDurationDisplayDate( + newStartDateMillis, + viewState.value?.durationInDays!! + ) ) } - private fun getCampaignDurationDisplayDate(startDate: Date, duration: Int): String { - val endDate = Date(startDate.time + duration * ONE_DAY_IN_MILLIS) - return "${startDate.formatToMMMdd()} - ${endDate.formatToMMMddYYYY()}" + private fun getCampaignDurationDisplayDate(startDateMillis: Long, duration: Int): String { + val endDate = Date(startDateMillis + duration * ONE_DAY_IN_MILLIS) + return "${Date(startDateMillis).formatToMMMdd()} - ${endDate.formatToMMMddYYYY()}" } private fun formatDailySpend(dailySpend: Float) = - currencyFormatter.formatCurrency(dailySpend.roundToInt().toBigDecimal(), BLAZE_DEFAULT_CURRENCY_CODE) + currencyFormatter.formatCurrency(dailySpend.roundToInt().toBigDecimal(), navArgs.currencyCode) private fun getBudgetRange(duration: Int) = duration * CAMPAIGN_MINIMUM_DAILY_SPEND..duration * CAMPAIGN_MAXIMUM_DAILY_SPEND @@ -127,7 +135,6 @@ class BlazeCampaignBudgetViewModel @Inject constructor( val currencyCode: String, val totalBudget: Float, val sliderValue: Float, - val spentBudget: Float, val budgetRange: ClosedFloatingPointRange, val dailySpending: String, val forecast: ForecastUi, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt index c96a134ce51..7845967eed2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewFragment.kt @@ -48,7 +48,12 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { is Exit -> findNavController().popBackStack() is NavigateToBudgetScreen -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections - .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignBudgetFragment() + .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignBudgetFragment( + event.totalBudget, + event.durationInDays, + event.campaignStartDateMillis, + event.currencyCode + ) ) is NavigateToEditAdScreen -> findNavController().navigateSafely( 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 4a2408a5a10..a6ea71e4a45 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 @@ -173,7 +173,16 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( budget = CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_budget), displayValue = budget.toDisplayValue(), - onItemSelected = { triggerEvent(NavigateToBudgetScreen) }, + onItemSelected = { + triggerEvent( + NavigateToBudgetScreen( + totalBudget = budget.totalBudget, + durationInDays = budget.durationInDays, + campaignStartDateMillis = budget.startDate.time, + currencyCode = budget.currencyCode + ) + ) + }, ), targetDetails = listOf( CampaignDetailItemUi( @@ -274,7 +283,13 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( val maxLinesValue: Int? = null, ) - object NavigateToBudgetScreen : MultiLiveEvent.Event() + data class NavigateToBudgetScreen( + val totalBudget: Float, + val durationInDays: Int, + val campaignStartDateMillis: Long, + val currencyCode: String + ) : MultiLiveEvent.Event() + data class NavigateToTargetSelectionScreen( val targetType: BlazeTargetType, val selectedIds: List diff --git a/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml b/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml index 2263c4218f8..3ab883e94ee 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml @@ -84,7 +84,20 @@ + android:label="BlazeCampaignBudgetFragment"> + + + + + Date: Wed, 7 Feb 2024 19:31:17 +0100 Subject: [PATCH 10/12] Update WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml Co-authored-by: Ondrej Ruttkay <0nko@users.noreply.github.com> --- .../main/res/navigation/nav_graph_blaze_campaign_creation.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml b/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml index e05a69e85c0..15d16fdb829 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_blaze_campaign_creation.xml @@ -93,7 +93,7 @@ app:argType="float" /> + app:argType="integer" /> From 3d7a5a261bb3a4e804bad943ce90be57148e01cd Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 8 Feb 2024 00:27:32 +0100 Subject: [PATCH 11/12] Avoid refreshing all the UI while draging the slider --- .../creation/budget/BlazeCampaignBudgetScreen.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index 8e74b08cd84..4544cc785d8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -112,7 +112,7 @@ private fun CampaignBudgetScreen( state.showCampaignDurationBottomSheet -> EditDurationBottomSheet( duration = state.durationInDays, startDateMillis = state.campaignStartDateMillis, - onDurationChanged = { onCampaignDurationUpdated(it.toInt()) }, + onDurationChanged = { onCampaignDurationUpdated(it) }, durationRange = state.durationRangeDays, onStartDateChanged = { onStartDateChanged(it) }, onApplyTapped = { coroutineScope.launch { modalSheetState.hide() } } @@ -306,7 +306,7 @@ private fun EditDurationBottomSheet( duration: Int, durationRange: ClosedFloatingPointRange, startDateMillis: Long, - onDurationChanged: (Float) -> Unit, + onDurationChanged: (Int) -> Unit, onStartDateChanged: (Long) -> Unit, onApplyTapped: () -> Unit = {}, modifier: Modifier = Modifier, @@ -323,6 +323,7 @@ private fun EditDurationBottomSheet( ) } + var sliderPosition by remember { mutableStateOf(duration) } Column(modifier = modifier.padding(16.dp)) { Text( text = stringResource(id = R.string.blaze_campaign_budget_duration_bottom_sheet_title), @@ -333,7 +334,7 @@ private fun EditDurationBottomSheet( modifier = Modifier .padding(top = 40.dp) .fillMaxWidth(), - text = stringResource(id = R.string.blaze_campaign_budget_duration_bottom_sheet_duration, duration), + text = stringResource(id = R.string.blaze_campaign_budget_duration_bottom_sheet_duration, sliderPosition), style = MaterialTheme.typography.subtitle1, fontWeight = FontWeight.SemiBold, textAlign = TextAlign.Center @@ -342,9 +343,10 @@ private fun EditDurationBottomSheet( modifier = Modifier .padding(top = 8.dp, bottom = 8.dp) .fillMaxWidth(), - value = duration.toFloat(), + value = sliderPosition.toFloat(), valueRange = durationRange, - onValueChange = onDurationChanged, + onValueChange = { sliderPosition = it.toInt() }, + onValueChangeFinished = { onDurationChanged(sliderPosition) }, colors = SliderDefaults.colors( inactiveTrackColor = colorResource(id = color.divider_color) ) From 37f26f90793e43c0cb952859cfcf5b2077708810 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 8 Feb 2024 00:39:27 +0100 Subject: [PATCH 12/12] Use floats for slider values for smoother sliding --- .../creation/budget/BlazeCampaignBudgetScreen.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt index 4544cc785d8..5eb895cc6fa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/budget/BlazeCampaignBudgetScreen.kt @@ -323,7 +323,7 @@ private fun EditDurationBottomSheet( ) } - var sliderPosition by remember { mutableStateOf(duration) } + var sliderPosition by remember { mutableStateOf(duration.toFloat()) } Column(modifier = modifier.padding(16.dp)) { Text( text = stringResource(id = R.string.blaze_campaign_budget_duration_bottom_sheet_title), @@ -334,7 +334,10 @@ private fun EditDurationBottomSheet( modifier = Modifier .padding(top = 40.dp) .fillMaxWidth(), - text = stringResource(id = R.string.blaze_campaign_budget_duration_bottom_sheet_duration, sliderPosition), + text = stringResource( + id = R.string.blaze_campaign_budget_duration_bottom_sheet_duration, + sliderPosition.toInt() + ), style = MaterialTheme.typography.subtitle1, fontWeight = FontWeight.SemiBold, textAlign = TextAlign.Center @@ -343,10 +346,10 @@ private fun EditDurationBottomSheet( modifier = Modifier .padding(top = 8.dp, bottom = 8.dp) .fillMaxWidth(), - value = sliderPosition.toFloat(), + value = sliderPosition, valueRange = durationRange, - onValueChange = { sliderPosition = it.toInt() }, - onValueChangeFinished = { onDurationChanged(sliderPosition) }, + onValueChange = { sliderPosition = it }, + onValueChangeFinished = { onDurationChanged(sliderPosition.toInt()) }, colors = SliderDefaults.colors( inactiveTrackColor = colorResource(id = color.divider_color) )