Skip to content

Commit f2535b0

Browse files
Merge branch 'trunk' into issue/10574-budget-screen-logic
# Conflicts: # WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewViewModel.kt
2 parents 4517321 + 541c2d0 commit f2535b0

22 files changed

+656
-201
lines changed

RELEASE-NOTES.txt

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
17.3
88
-----
99
- [*] [Internal] Enhanced user experience in shipping label creation with automatic scrolling to the first invalid field upon form submission failure [https://github.com/woocommerce/woocommerce-android/pull/10657]
10+
- [*] [Internal] Enhanced product variation delete confirmation dialog visibility and functionality across device rotations [https://github.com/woocommerce/woocommerce-android/pull/10664]
1011
- [*] [Internal] Added a text along with the receipts file when it is shared [https://github.com/woocommerce/woocommerce-android/pull/10681]
1112

1213
17.2

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/AnalyticsHubViewModel.kt

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class AnalyticsHubViewModel @Inject constructor(
148148
}
149149

150150
private fun trackSeeReportInteraction(card: ReportCard) {
151+
onTrackableUIInteraction()
151152
val period = ranges.selectionType.identifier
152153
val report = card.name.lowercase()
153154
tracker.track(

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

+69-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.woocommerce.android.ui.blaze
22

33
import android.os.Parcelable
4+
import com.woocommerce.android.OnChangedException
45
import com.woocommerce.android.tools.SelectedSite
56
import com.woocommerce.android.ui.products.ProductDetailRepository
67
import com.woocommerce.android.util.TimezoneProvider
8+
import com.woocommerce.android.util.WooLog
79
import kotlinx.coroutines.flow.map
810
import kotlinx.parcelize.Parcelize
9-
import org.wordpress.android.fluxc.persistence.blaze.BlazeCampaignsDao.BlazeAdSuggestionEntity
11+
import org.wordpress.android.fluxc.model.blaze.BlazeAdSuggestion
1012
import org.wordpress.android.fluxc.store.blaze.BlazeCampaignsStore
1113
import java.util.Date
1214
import javax.inject.Inject
@@ -29,44 +31,95 @@ class BlazeRepository @Inject constructor(
2931
fun observeLanguages() = blazeCampaignsStore.observeBlazeTargetingLanguages()
3032
.map { it.map { language -> Language(language.id, language.name) } }
3133

32-
suspend fun fetchLanguages() = blazeCampaignsStore.fetchBlazeTargetingLanguages()
34+
suspend fun fetchLanguages(): Result<Unit> {
35+
val result = blazeCampaignsStore.fetchBlazeTargetingLanguages(selectedSite.get())
36+
37+
return when {
38+
result.isError -> {
39+
WooLog.w(WooLog.T.BLAZE, "Failed to fetch languages: ${result.error}")
40+
Result.failure(OnChangedException(result.error))
41+
}
42+
else -> Result.success(Unit)
43+
}
44+
}
3345

3446
fun observeDevices() = blazeCampaignsStore.observeBlazeTargetingDevices()
3547
.map { it.map { device -> Device(device.id, device.name) } }
3648

37-
suspend fun fetchDevices() = blazeCampaignsStore.fetchBlazeTargetingDevices()
49+
suspend fun fetchDevices(): Result<Unit> {
50+
val result = blazeCampaignsStore.fetchBlazeTargetingDevices(selectedSite.get())
51+
52+
return when {
53+
result.isError -> {
54+
WooLog.w(WooLog.T.BLAZE, "Failed to fetch devices: ${result.error}")
55+
Result.failure(OnChangedException(result.error))
56+
}
57+
else -> Result.success(Unit)
58+
}
59+
}
3860

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

42-
suspend fun fetchInterests() = blazeCampaignsStore.fetchBlazeTargetingTopics()
64+
suspend fun fetchInterests(): Result<Unit> {
65+
val result = blazeCampaignsStore.fetchBlazeTargetingTopics(selectedSite.get())
4366

44-
suspend fun fetchLocations(query: String) = blazeCampaignsStore.fetchBlazeTargetingLocations(query).model
45-
?.map { location -> Location(location.id, location.name, location.parent?.name, location.type) }
67+
return when {
68+
result.isError -> {
69+
WooLog.w(WooLog.T.BLAZE, "Failed to fetch interests: ${result.error}")
70+
Result.failure(OnChangedException(result.error))
71+
}
72+
else -> Result.success(Unit)
73+
}
74+
}
75+
76+
suspend fun fetchLocations(query: String): Result<List<Location>> {
77+
val result = blazeCampaignsStore.fetchBlazeTargetingLocations(
78+
selectedSite.get(),
79+
query
80+
)
81+
82+
return when {
83+
result.isError -> {
84+
WooLog.w(WooLog.T.BLAZE, "Failed to fetch locations: ${result.error}")
85+
Result.failure(OnChangedException(result.error))
86+
}
87+
else -> Result.success(
88+
result.model?.map { location ->
89+
Location(location.id, location.name, location.parent?.name, location.type)
90+
} ?: emptyList()
91+
)
92+
}
93+
}
4694

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

49-
suspend fun getAdSuggestions(productId: Long): List<AiSuggestionForAd>? {
50-
fun List<BlazeAdSuggestionEntity>.mapToUiModel(): List<AiSuggestionForAd> {
97+
suspend fun fetchAdSuggestions(productId: Long): Result<List<AiSuggestionForAd>> {
98+
fun List<BlazeAdSuggestion>.mapToUiModel(): List<AiSuggestionForAd> {
5199
return map { AiSuggestionForAd(it.tagLine, it.description) }
52100
}
53101

54-
val suggestions = blazeCampaignsStore.getBlazeAdSuggestions(selectedSite.get(), productId)
55-
return if (suggestions.isNotEmpty()) {
56-
suggestions.mapToUiModel()
57-
} else {
58-
blazeCampaignsStore.fetchBlazeAdSuggestions(selectedSite.get(), productId).model?.mapToUiModel()
102+
val result = blazeCampaignsStore.fetchBlazeAdSuggestions(selectedSite.get(), productId)
103+
104+
return when {
105+
result.isError -> {
106+
WooLog.w(WooLog.T.BLAZE, "Failed to fetch ad suggestions: ${result.error}")
107+
Result.failure(OnChangedException(result.error))
108+
}
109+
else -> Result.success(result.model?.mapToUiModel() ?: emptyList())
59110
}
60111
}
61112

62-
fun getCampaignPreviewDetails(productId: Long): CampaignPreview {
113+
suspend fun getCampaignPreviewDetails(productId: Long): CampaignPreview {
63114
val product = productDetailRepository.getProduct(productId)
115+
?: productDetailRepository.fetchProductOrLoadFromCache(productId)!!
116+
64117
return CampaignPreview(
65118
productId = productId,
66119
userTimeZone = timezoneProvider.deviceTimezone.displayName,
67-
targetUrl = product?.permalink ?: "",
120+
targetUrl = product.permalink,
68121
urlParams = null,
69-
campaignImageUrl = product?.firstImageUrl
122+
campaignImageUrl = product.firstImageUrl
70123
)
71124
}
72125

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor(
4343

4444
private fun loadSuggestions() {
4545
viewModelScope.launch {
46-
blazeRepository.getAdSuggestions(navArgs.productId)?.let { list ->
46+
blazeRepository.fetchAdSuggestions(navArgs.productId).getOrNull()?.let { list ->
4747
val index = list.indexOfFirst { it.tagLine == navArgs.tagline && it.description == navArgs.description }
4848
val suggestions = list.map { AiSuggestionForAd(it.tagLine, it.description) }
4949
if (index != -1) {

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt

+128-7
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
11
package com.woocommerce.android.ui.blaze.creation.destination
22

3+
import androidx.annotation.StringRes
34
import androidx.compose.foundation.background
45
import androidx.compose.foundation.clickable
6+
import androidx.compose.foundation.interaction.MutableInteractionSource
7+
import androidx.compose.foundation.layout.Arrangement
58
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Row
610
import androidx.compose.foundation.layout.fillMaxSize
711
import androidx.compose.foundation.layout.fillMaxWidth
812
import androidx.compose.foundation.layout.padding
913
import androidx.compose.material.Divider
1014
import androidx.compose.material.MaterialTheme
15+
import androidx.compose.material.RadioButton
16+
import androidx.compose.material.RadioButtonDefaults
1117
import androidx.compose.material.Scaffold
1218
import androidx.compose.material.Text
1319
import androidx.compose.material.icons.Icons.Filled
1420
import androidx.compose.material.icons.filled.ArrowBack
1521
import androidx.compose.runtime.Composable
22+
import androidx.compose.runtime.getValue
1623
import androidx.compose.runtime.livedata.observeAsState
24+
import androidx.compose.runtime.mutableStateOf
25+
import androidx.compose.runtime.remember
26+
import androidx.compose.runtime.saveable.rememberSaveable
27+
import androidx.compose.runtime.setValue
1728
import androidx.compose.ui.Modifier
1829
import androidx.compose.ui.res.colorResource
1930
import androidx.compose.ui.res.dimensionResource
2031
import androidx.compose.ui.res.stringResource
2132
import androidx.compose.ui.text.style.TextOverflow
33+
import androidx.compose.ui.window.Dialog
2234
import com.woocommerce.android.R
2335
import com.woocommerce.android.ui.blaze.creation.destination.BlazeCampaignCreationAdDestinationViewModel.ViewState
36+
import com.woocommerce.android.ui.compose.component.DialogButtonsRowLayout
2437
import com.woocommerce.android.ui.compose.component.Toolbar
38+
import com.woocommerce.android.ui.compose.component.WCTextButton
2539
import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews
2640
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
2741

@@ -32,7 +46,8 @@ fun BlazeCampaignCreationAdDestinationScreen(viewModel: BlazeCampaignCreationAdD
3246
previewState,
3347
viewModel::onBackPressed,
3448
viewModel::onUrlPropertyTapped,
35-
viewModel::onParameterPropertyTapped
49+
viewModel::onParameterPropertyTapped,
50+
viewModel::onDestinationUrlChanged
3651
)
3752
}
3853
}
@@ -42,7 +57,8 @@ fun AdDestinationScreen(
4257
viewState: ViewState,
4358
onBackPressed: () -> Unit,
4459
onUrlPropertyTapped: () -> Unit,
45-
onParametersPropertyTapped: () -> Unit
60+
onParametersPropertyTapped: () -> Unit,
61+
onDestinationUrlChanged: (String) -> Unit
4662
) {
4763
Scaffold(
4864
topBar = {
@@ -62,7 +78,7 @@ fun AdDestinationScreen(
6278
) {
6379
AdDestinationProperty(
6480
title = stringResource(id = R.string.blaze_campaign_edit_ad_destination_url_property_title),
65-
value = viewState.destinationUrl,
81+
value = viewState.targetUrl,
6682
onPropertyTapped = onUrlPropertyTapped
6783
)
6884
Divider()
@@ -72,13 +88,22 @@ fun AdDestinationScreen(
7288
onPropertyTapped = onParametersPropertyTapped
7389
)
7490
}
91+
92+
if (viewState.isUrlDialogVisible) {
93+
AdDestinationUrlDialog(
94+
viewState,
95+
onDismissed = { onDestinationUrlChanged(viewState.targetUrl) },
96+
onSaveTapped = onDestinationUrlChanged
97+
)
98+
}
7599
}
76100
}
77101

78102
@Composable
79103
fun AdDestinationProperty(title: String, value: String, onPropertyTapped: () -> Unit) {
80104
Column(
81-
modifier = Modifier
105+
modifier =
106+
Modifier
82107
.clickable { onPropertyTapped() }
83108
.padding(dimensionResource(id = R.dimen.major_100))
84109
.fillMaxWidth(1f)
@@ -98,18 +123,114 @@ fun AdDestinationProperty(title: String, value: String, onPropertyTapped: () ->
98123
}
99124
}
100125

126+
@Composable
127+
fun AdDestinationUrlDialog(
128+
viewState: ViewState,
129+
onDismissed: () -> Unit,
130+
onSaveTapped: (String) -> Unit,
131+
) {
132+
Dialog(onDismissRequest = onDismissed) {
133+
Column(
134+
modifier =
135+
Modifier
136+
.background(MaterialTheme.colors.surface, MaterialTheme.shapes.medium)
137+
.padding(dimensionResource(id = R.dimen.major_100)),
138+
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.major_100))
139+
) {
140+
Text(
141+
text = stringResource(id = R.string.blaze_campaign_edit_ad_destination_url_property_title),
142+
style = MaterialTheme.typography.h6
143+
)
144+
145+
var targetUrl by rememberSaveable {
146+
mutableStateOf(viewState.targetUrl)
147+
}
148+
149+
UrlOption(
150+
url = viewState.productUrl,
151+
targetUrl = targetUrl,
152+
title = R.string.blaze_campaign_edit_ad_destination_product_url_option
153+
) {
154+
targetUrl = viewState.productUrl
155+
}
156+
157+
UrlOption(
158+
url = viewState.siteUrl,
159+
targetUrl = targetUrl,
160+
title = R.string.blaze_campaign_edit_ad_destination_site_url_option
161+
) {
162+
targetUrl = viewState.siteUrl
163+
}
164+
165+
DialogButtonsRowLayout(
166+
confirmButton = {
167+
WCTextButton(onClick = {
168+
onSaveTapped(targetUrl)
169+
}) {
170+
Text(text = stringResource(id = R.string.save))
171+
}
172+
},
173+
dismissButton = {
174+
WCTextButton(onClick = onDismissed) {
175+
Text(text = stringResource(id = android.R.string.cancel))
176+
}
177+
},
178+
neutralButton = null
179+
)
180+
}
181+
}
182+
}
183+
184+
@Composable
185+
private fun UrlOption(
186+
url: String,
187+
targetUrl: String,
188+
@StringRes title: Int,
189+
onOptionSelected: () -> Unit
190+
) {
191+
val interactionSource = remember { MutableInteractionSource() }
192+
Row(
193+
modifier = Modifier.clickable(
194+
indication = null,
195+
interactionSource = interactionSource,
196+
onClick = onOptionSelected
197+
),
198+
) {
199+
RadioButton(
200+
selected = url == targetUrl,
201+
onClick = onOptionSelected,
202+
colors = RadioButtonDefaults.colors(
203+
selectedColor = MaterialTheme.colors.primary,
204+
unselectedColor = colorResource(id = R.color.color_on_surface_medium)
205+
)
206+
)
207+
Column {
208+
Text(stringResource(title))
209+
Text(
210+
text = url,
211+
style = MaterialTheme.typography.subtitle1,
212+
color = colorResource(id = R.color.color_on_surface_medium)
213+
)
214+
}
215+
}
216+
}
217+
101218
@LightDarkThemePreviews
102219
@Composable
103220
fun PreviewAdDestinationScreen() {
104221
WooThemeWithBackground {
105222
AdDestinationScreen(
106223
viewState = ViewState(
107-
destinationUrl = "https://woocommerce.com",
108-
parameters = "utm_source=woocommerce\nutm_medium=android\nutm_campaign=blaze"
224+
parameters = "utm_source=woocommerce\nutm_medium=android\nutm_campaign=blaze",
225+
productUrl = "https://woocommerce.com/products/1",
226+
siteUrl = "https://woocommerce.com",
227+
targetUrl = "https://woocommerce.com/products/12",
228+
isUrlDialogVisible = true
109229
),
110230
onBackPressed = {},
111231
onUrlPropertyTapped = {},
112-
onParametersPropertyTapped = {}
232+
onParametersPropertyTapped = {},
233+
onDestinationUrlChanged = {}
113234
)
114235
}
115236
}

0 commit comments

Comments
 (0)