From 78d135e3f5dcc21443130a8a6a9f920c2bd29391 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Tue, 23 Jan 2024 20:19:38 -0500 Subject: [PATCH 001/119] Fixed issue by using temp lists of keys to remove to prevent race issues --- .../push/NotificationMessageHandler.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt index 64b4521a209..53cf58987b1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt @@ -259,18 +259,19 @@ class NotificationMessageHandler @Inject constructor( @Synchronized fun removeNotificationByNotificationIdFromSystemsBar(localPushId: Int) { - val keptNotifs = HashMap() - ACTIVE_NOTIFICATIONS_MAP.asSequence() - .forEach { row -> - if (row.key == localPushId) { - notificationBuilder.cancelNotification(row.key) - } else { - keptNotifs[row.key] = row.value - } + val keysToRemove = mutableListOf() + + ACTIVE_NOTIFICATIONS_MAP.forEach { (key, _) -> + if (key == localPushId) { + keysToRemove.add(key) } + } + + keysToRemove.forEach { key -> + notificationBuilder.cancelNotification(key) + ACTIVE_NOTIFICATIONS_MAP.remove(key) + } - clearNotifications() - ACTIVE_NOTIFICATIONS_MAP.putAll(keptNotifs) updateNotificationsState() } From 2886c483ad0632d72812d1ba5645a58b48ae3dd7 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Mon, 5 Feb 2024 19:21:09 -0500 Subject: [PATCH 002/119] Revert unneeded fix for concurrency issue in the handler. --- .../push/NotificationMessageHandler.kt | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt index 53cf58987b1..64b4521a209 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt @@ -259,19 +259,18 @@ class NotificationMessageHandler @Inject constructor( @Synchronized fun removeNotificationByNotificationIdFromSystemsBar(localPushId: Int) { - val keysToRemove = mutableListOf() - - ACTIVE_NOTIFICATIONS_MAP.forEach { (key, _) -> - if (key == localPushId) { - keysToRemove.add(key) + val keptNotifs = HashMap() + ACTIVE_NOTIFICATIONS_MAP.asSequence() + .forEach { row -> + if (row.key == localPushId) { + notificationBuilder.cancelNotification(row.key) + } else { + keptNotifs[row.key] = row.value + } } - } - - keysToRemove.forEach { key -> - notificationBuilder.cancelNotification(key) - ACTIVE_NOTIFICATIONS_MAP.remove(key) - } + clearNotifications() + ACTIVE_NOTIFICATIONS_MAP.putAll(keptNotifs) updateNotificationsState() } From 0a990af2facc08f925a514d65cea6f7cc68c415d Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Mon, 5 Feb 2024 19:22:16 -0500 Subject: [PATCH 003/119] Removed the offloading of this notification work to another thread. --- .../android/notifications/NotificationsProcessingService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/NotificationsProcessingService.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/NotificationsProcessingService.kt index 2da6868d3ec..b9d7fc6202a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/NotificationsProcessingService.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/NotificationsProcessingService.kt @@ -68,9 +68,8 @@ class NotificationsProcessingService : Service() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - // Offload to a separate thread. actionProcessor = ActionProcessor(intent, startId) - Thread { actionProcessor.process() }.start() + actionProcessor.process() return START_NOT_STICKY } From 8df0cc77a55ed9e8606d2886cf929d421e571652 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Tue, 6 Feb 2024 20:02:37 -0500 Subject: [PATCH 004/119] Removed synchronized from methods since notif is called from main thread --- .../android/notifications/push/NotificationMessageHandler.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt index 64b4521a209..7dae21b1090 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/NotificationMessageHandler.kt @@ -58,12 +58,10 @@ class NotificationMessageHandler @Inject constructor( private val ACTIVE_NOTIFICATIONS_MAP = mutableMapOf() } - @Synchronized fun onPushNotificationDismissed(notificationId: Int) { removeNotificationByNotificationIdFromSystemsBar(notificationId) } - @Synchronized fun onLocalNotificationDismissed(notificationId: Int, notificationType: String) { removeNotificationByNotificationIdFromSystemsBar(notificationId) AnalyticsTracker.track( @@ -240,7 +238,6 @@ class NotificationMessageHandler @Inject constructor( notificationBuilder.cancelAllNotifications() } - @Synchronized fun removeNotificationByRemoteIdFromSystemsBar(remoteNoteId: Long) { val keptNotifs = HashMap() ACTIVE_NOTIFICATIONS_MAP.asSequence() @@ -257,7 +254,6 @@ class NotificationMessageHandler @Inject constructor( updateNotificationsState() } - @Synchronized fun removeNotificationByNotificationIdFromSystemsBar(localPushId: Int) { val keptNotifs = HashMap() ACTIVE_NOTIFICATIONS_MAP.asSequence() @@ -274,7 +270,6 @@ class NotificationMessageHandler @Inject constructor( updateNotificationsState() } - @Synchronized fun removeNotificationsOfTypeFromSystemsBar(type: NotificationChannelType, remoteSiteId: Long) { val keptNotifs = HashMap() // Using a copy of the map to avoid concurrency problems From 2f448b253d54e15dcc6aa1bfc8275cfdff8b3824 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 7 Feb 2024 16:51:12 +0100 Subject: [PATCH 005/119] build: migrate from `kapt` to `ksp` --- WooCommerce/build.gradle | 12 ++++++------ build.gradle | 1 + gradle.properties-example | 3 --- settings.gradle | 3 ++- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 3dffa9f78c5..aee1163b234 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -4,13 +4,13 @@ import se.bjurr.violations.lib.model.SEVERITY plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' - id 'org.jetbrains.kotlin.kapt' id 'org.jetbrains.kotlin.plugin.parcelize' id 'com.google.dagger.hilt.android' id 'se.bjurr.violations.violation-comments-to-github-gradle-plugin' id 'io.sentry.android.gradle' id 'androidx.navigation.safeargs.kotlin' id 'com.google.gms.google-services' + id 'com.google.devtools.ksp' } sentry { @@ -270,15 +270,15 @@ dependencies { implementation "androidx.hilt:hilt-common:$hiltJetpackVersion" implementation "androidx.hilt:hilt-work:$hiltJetpackVersion" - kapt "androidx.hilt:hilt-compiler:$hiltJetpackVersion" - kapt "com.google.dagger:hilt-compiler:$gradle.ext.daggerVersion" + ksp "androidx.hilt:hilt-compiler:$hiltJetpackVersion" + ksp "com.google.dagger:hilt-compiler:$gradle.ext.daggerVersion" implementation "com.google.dagger:dagger-android-support:$gradle.ext.daggerVersion" - kapt "com.google.dagger:dagger-android-processor:$gradle.ext.daggerVersion" + ksp "com.google.dagger:dagger-android-processor:$gradle.ext.daggerVersion" implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' implementation "com.github.bumptech.glide:glide:$glideVersion" - kapt "com.github.bumptech.glide:compiler:$glideVersion" + ksp "com.github.bumptech.glide:compiler:$glideVersion" implementation "com.github.bumptech.glide:volley-integration:$glideVersion@aar" implementation 'com.google.android.play:app-update-ktx:2.1.0' implementation 'com.google.android.play:review-ktx:2.0.1' @@ -323,7 +323,7 @@ dependencies { androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion" androidTestImplementation "com.google.dagger:hilt-android-testing:$gradle.ext.daggerVersion" - kaptAndroidTest "com.google.dagger:hilt-android-compiler:$gradle.ext.daggerVersion" + kspAndroidTest "com.google.dagger:hilt-android-compiler:$gradle.ext.daggerVersion" androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' // Dependencies for screenshots diff --git a/build.gradle b/build.gradle index 72eaa5fba07..99c973af8bd 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ plugins { id 'org.jetbrains.kotlin.android' apply false id 'com.google.dagger.hilt.android' apply false id 'com.google.gms.google-services' apply false + id 'com.google.devtools.ksp' apply false } measureBuilds { diff --git a/gradle.properties-example b/gradle.properties-example index a7befacff65..176cf74ab6b 100644 --- a/gradle.properties-example +++ b/gradle.properties-example @@ -48,9 +48,6 @@ wc.e2e.real.api.password = password # The type of in app update FLEXIBLE = 0; IMMEDIATE = 1 wc.in_app_update_type = 0 -# Necessary for gradle 5 -kapt.incremental.apt=false - # Measuring build times tracksEnabled = false #tracksUsername = diff --git a/settings.gradle b/settings.gradle index b8da08eaa6b..374433859e1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ pluginManagement { gradle.ext.daggerVersion = '2.50' gradle.ext.detektVersion = '1.19.0' gradle.ext.kotlinVersion = '1.9.22' + gradle.ext.kspVersion = '1.9.22-1.0.17' gradle.ext.measureBuildsVersion = '2.0.3' gradle.ext.navigationVersion = '2.6.0' gradle.ext.sentryVersion = '3.5.0' @@ -35,10 +36,10 @@ pluginManagement { id 'io.sentry.android.gradle' version gradle.ext.sentryVersion id 'org.jetbrains.kotlin.android' version gradle.ext.kotlinVersion id 'org.jetbrains.kotlin.jvm' version gradle.ext.kotlinVersion - id 'org.jetbrains.kotlin.kapt' version gradle.ext.kotlinVersion id 'org.jetbrains.kotlin.plugin.parcelize' version gradle.ext.kotlinVersion id 'se.bjurr.violations.violation-comments-to-github-gradle-plugin' version gradle.ext.violationCommentsVersion id 'com.google.dagger.hilt.android' version gradle.ext.daggerVersion + id 'com.google.devtools.ksp' version gradle.ext.kspVersion } } From 610eca2879b766b11c85d8221792719ad9c1b5f6 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 7 Feb 2024 16:51:58 +0100 Subject: [PATCH 006/119] build: remove `ProductImageMapModul` `ProductImageMap` uses constructor injection. This module wasn't used at all and KSP was throwing the following error: [Hilt] com.woocommerce.android.di.ProductImageMapModule is missing an @InstallIn annotation. If this was intentional, see https://dagger.dev/hilt/flags#disable-install-in-check for how to disable this check. --- .../android/di/ProductImageMapModule.kt | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/di/ProductImageMapModule.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/ProductImageMapModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/ProductImageMapModule.kt deleted file mode 100644 index 69b9c730856..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/ProductImageMapModule.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.woocommerce.android.di - -import com.woocommerce.android.tools.ProductImageMap -import com.woocommerce.android.tools.SelectedSite -import com.woocommerce.android.util.CoroutineDispatchers -import dagger.Module -import dagger.Provides -import kotlinx.coroutines.CoroutineScope -import org.wordpress.android.fluxc.store.WCProductStore -import javax.inject.Singleton - -@Module -class ProductImageMapModule { - @Provides - @Singleton - fun provideProductImageMap( - selectedSite: SelectedSite, - productStore: WCProductStore, - @AppCoroutineScope appCoroutineScope: CoroutineScope, - coroutineDispatchers: CoroutineDispatchers, - ) = ProductImageMap(selectedSite, productStore, appCoroutineScope, coroutineDispatchers) -} From 17cb274f1599e09896bf06396251a5d68ba1c713 Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 8 Feb 2024 15:43:07 +0100 Subject: [PATCH 007/119] Prep for 2 pane layout --- .../main/res/layout/fragment_product_list.xml | 215 ++++++++++-------- 1 file changed, 120 insertions(+), 95 deletions(-) diff --git a/WooCommerce/src/main/res/layout/fragment_product_list.xml b/WooCommerce/src/main/res/layout/fragment_product_list.xml index 55f8d7a9f5e..209134277ee 100644 --- a/WooCommerce/src/main/res/layout/fragment_product_list.xml +++ b/WooCommerce/src/main/res/layout/fragment_product_list.xml @@ -1,112 +1,137 @@ - - + - - + android:layout_height="match_parent"> - - + + - - + + - + + - + - + + + + + + + - + + - + - - + From 06989e0dbd631c6258681d31bb69398077cb7a4e Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 9 Feb 2024 15:34:02 +0100 Subject: [PATCH 008/119] Add destination URL parameters screen boilerplate --- ...CreationAdDestinationParametersFragment.kt | 43 ++++ ...gnCreationAdDestinationParametersScreen.kt | 214 ++++++++++++++++++ ...reationAdDestinationParametersViewModel.kt | 79 +++++++ 3 files changed, 336 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersFragment.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersFragment.kt new file mode 100644 index 00000000000..e01298560c0 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersFragment.kt @@ -0,0 +1,43 @@ +package com.woocommerce.android.ui.blaze.creation.destination + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +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.Event.ExitWithResult +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class BlazeCampaignCreationAdDestinationParametersFragment : BaseFragment() { + companion object { + const val BLAZE_DESTINATION_PARAMETERS_RESULT = "blaze_destination_parameters_result" + } + + private val viewModel: BlazeCampaignCreationAdDestinationParametersViewModel by viewModels() + + override val activityAppBarStatus: AppBarStatus + get() = AppBarStatus.Hidden + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return composeView { + BlazeCampaignCreationAdDestinationParametersScreen(viewModel) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + handleEvents() + } + + private fun handleEvents() { + viewModel.event.observe(viewLifecycleOwner) { event -> + when (event) { + is ExitWithResult<*> -> navigateBackWithResult(BLAZE_DESTINATION_PARAMETERS_RESULT, event.data) + } + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt new file mode 100644 index 00000000000..e5325369148 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt @@ -0,0 +1,214 @@ +package com.woocommerce.android.ui.blaze.creation.destination + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.Icons.Filled +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.DeleteOutline +import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import com.woocommerce.android.R +import com.woocommerce.android.ui.blaze.creation.destination.BlazeCampaignCreationAdDestinationParametersViewModel.ViewState +import com.woocommerce.android.ui.compose.component.Toolbar +import com.woocommerce.android.ui.compose.component.WCTextButton +import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground + +@Composable +fun BlazeCampaignCreationAdDestinationParametersScreen( + viewModel: BlazeCampaignCreationAdDestinationParametersViewModel +) { + viewModel.viewState.observeAsState().value?.let { viewState -> + AdDestinationParametersScreen( + viewState, + viewModel::onBackPressed, + viewModel::onAddParameterTapped, + viewModel::onParameterTapped, + viewModel::onDeleteParameterTapped + ) + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun AdDestinationParametersScreen( + viewState: ViewState, + onBackPressed: () -> Unit, + onAddParameterTapped: () -> Unit, + onParameterTapped: (String) -> Unit, + onDeleteParameterTapped: (String) -> Unit +) { + Scaffold( + topBar = { + Toolbar( + title = stringResource(id = R.string.blaze_campaign_edit_ad_destination_parameters_property_title), + onNavigationButtonClick = onBackPressed, + navigationIcon = Filled.ArrowBack + ) + }, + modifier = Modifier.background(MaterialTheme.colors.surface) + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .background(MaterialTheme.colors.surface) + .padding(paddingValues) + .fillMaxSize(), + ) { + item { + WCTextButton( + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(id = R.dimen.minor_50)), + onClick = onAddParameterTapped, + text = stringResource(id = R.string.blaze_campaign_edit_ad_destination_add_parameter_button), + icon = Icons.Default.Add + ) + } + + itemsIndexed( + items = viewState.parameters.entries.toList(), + key = { _, item -> item.key } + ) { index, (key, value) -> + Column( + modifier = Modifier + .animateItemPlacement() + .fillMaxWidth() + ) { + Row(modifier = Modifier + .clickable { onParameterTapped(key) } + .padding( + start = dimensionResource(id = R.dimen.major_100), + top = dimensionResource(id = R.dimen.minor_100), + bottom = dimensionResource(id = R.dimen.minor_100) + ), + verticalAlignment = CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = key, + style = MaterialTheme.typography.body2, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Bold + ) + Text( + text = value, + style = MaterialTheme.typography.body2, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = colorResource(id = R.color.color_on_surface_medium) + ) + } + IconButton( + onClick = { onDeleteParameterTapped(key) } + ) { + Icon( + imageVector = Icons.Default.DeleteOutline, + contentDescription = stringResource(id = R.string.delete), + tint = colorResource(id = R.color.color_on_surface_medium) + ) + } + } + + if (index < viewState.parameters.size) { + Divider( + modifier = Modifier + .fillMaxWidth() + ) + } + } + } + + item { + Text( + modifier = Modifier + .padding( + start = dimensionResource(id = R.dimen.major_100), + end = dimensionResource(id = R.dimen.major_100), + top = dimensionResource(id = R.dimen.major_100), + bottom = dimensionResource(id = R.dimen.minor_100) + ), + text = stringResource( + R.string.blaze_campaign_edit_ad_characters_remaining, + viewState.charactersRemaining + ), + style = MaterialTheme.typography.caption, + color = colorResource(id = R.color.color_on_surface_medium) + ) + Text( + modifier = Modifier + .padding( + horizontal = dimensionResource(id = R.dimen.major_100), + ), + text = stringResource( + R.string.blaze_campaign_edit_ad_destination_destination_with_parameters, + viewState.url + ), + style = MaterialTheme.typography.caption, + color = colorResource(id = R.color.color_on_surface_medium) + ) + } + } + } +} + +@LightDarkThemePreviews +@Composable +fun PreviewAdDestinationParametersScreen() { + WooThemeWithBackground { + AdDestinationParametersScreen( + viewState = ViewState( + baseUrl = "https://woocommerce.com", + parameters = mapOf( + "utm_source" to "woocommerce", + "utm_medium" to "android", + "utm_campaign" to "blaze" + ) + ), + onBackPressed = {}, + onAddParameterTapped = {}, + onParameterTapped = {}, + onDeleteParameterTapped = {} + ) + } +} + +@LightDarkThemePreviews +@Composable +fun PreviewEmptyAdDestinationParametersScreen() { + WooThemeWithBackground { + AdDestinationParametersScreen( + viewState = ViewState( + baseUrl = "https://woocommerce.com?utm_source=woocommerce&utm_medium=android&utm_campaign=blaze", + parameters = emptyMap() + ), + onBackPressed = {}, + onAddParameterTapped = {}, + onParameterTapped = {}, + onDeleteParameterTapped = {} + ) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt new file mode 100644 index 00000000000..2de4dd43771 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt @@ -0,0 +1,79 @@ +package com.woocommerce.android.ui.blaze.creation.destination + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.asLiveData +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.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +@HiltViewModel +class BlazeCampaignCreationAdDestinationParametersViewModel @Inject constructor( + savedStateHandle: SavedStateHandle +) : ScopedViewModel(savedStateHandle) { + companion object { + // The maximum number of characters allowed in a URL by Chrome + private const val MAX_CHARACTERS = 2083 + } + private val navArgs: BlazeCampaignCreationAdDestinationParametersFragmentArgs by savedStateHandle.navArgs() + + private val _viewState = MutableStateFlow( + ViewState( + baseUrl = getBaseUrl(navArgs.url), + parameters = parseParameters(navArgs.url) + ) + ) + + val viewState = _viewState.asLiveData() + + fun onBackPressed() { + triggerEvent(ExitWithResult(_viewState.value.url)) + } + + fun onAddParameterTapped() { + /* TODO */ + } + + @Suppress("UNUSED_PARAMETER") + fun onParameterTapped(key: String) { + /* TODO: */ + } + + fun onDeleteParameterTapped(key: String) { + _viewState.update { + it.copy(parameters = it.parameters - key) + } + } + + private fun getBaseUrl(url: String): String { + return url.split("?").getOrNull(0) ?: url + } + + private fun parseParameters(url: String): Map { + val parameters = url.split("?").getOrNull(1) ?: return emptyMap() + return parameters.split("&").associate { + val (key, value) = it.split("=") + key to value + } + } + + data class ViewState( + private val baseUrl: String, + val parameters: Map + ) { + val url: String + get() = buildString { + append(baseUrl) + append("?") + append(parameters.entries.joinToString("&") { (key, value) -> + "$key=$value" + }) + } + + val charactersRemaining: Int + get() = MAX_CHARACTERS - url.length + } +} From 78f6dad0943b3f8526f8ae0a61e2a7336079c747 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 9 Feb 2024 15:34:18 +0100 Subject: [PATCH 009/119] Add string resources --- WooCommerce/src/main/res/values/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index fcae5208c39..0677af0d45d 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3890,6 +3890,8 @@ URL parameters The product URL The site home + Add parameter + Destination: %s From 9e010ba5a8b450cbd46541487039a9fa87983449 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 9 Feb 2024 15:34:52 +0100 Subject: [PATCH 010/119] Add parameter handling logic to ad destination view model --- ...eCampaignCreationAdDestinationViewModel.kt | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt index 274335dcff3..9e6ced36b7d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt @@ -2,11 +2,10 @@ package com.woocommerce.android.ui.blaze.creation.destination import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData -import com.woocommerce.android.R import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.products.ProductDetailRepository +import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit -import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel @@ -17,7 +16,6 @@ import javax.inject.Inject @HiltViewModel class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - resourceProvider: ResourceProvider, selectedSite: SelectedSite, productDetailRepository: ProductDetailRepository ) : ScopedViewModel(savedStateHandle) { @@ -27,11 +25,9 @@ class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( private val _viewState = MutableStateFlow( ViewState( - parameters = resourceProvider - .getString(R.string.blaze_campaign_edit_ad_destination_empty_parameters_message), productUrl = productUrl, siteUrl = selectedSite.get().url, - targetUrl = navArgs.targetUrl, + targetUrl = navArgs.targetUrl + "?utm_source=woocommerce_android&utm_medium=ad&utm_campaign=blaze", isUrlDialogVisible = false ) ) @@ -47,7 +43,7 @@ class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( } fun onParameterPropertyTapped() { - /* TODO */ + triggerEvent(NavigateToParametersScreen(_viewState.value.targetUrl)) } fun onDestinationUrlChanged(destinationUrl: String) { @@ -58,10 +54,19 @@ class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( } data class ViewState( - val parameters: String, val productUrl: String, val siteUrl: String, val targetUrl: String, val isUrlDialogVisible: Boolean - ) + ) { + val parameters: String? + get() = getParameters(targetUrl) + + private fun getParameters(url: String): String? { + val parameters = url.split("?").getOrNull(1) ?: return null + return parameters.replace("&", "\n") + } + } + + data class NavigateToParametersScreen(val url: String) : MultiLiveEvent.Event() } From 5afedb581c1e78c9f3f4bd21f5188d0922ca20ad Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 9 Feb 2024 15:35:11 +0100 Subject: [PATCH 011/119] Add navigation and result handling --- .../BlazeCampaignCreationAdDestinationFragment.kt | 13 ++++++++++++- .../BlazeCampaignCreationAdDestinationScreen.kt | 7 ++++--- .../nav_graph_blaze_campaign_creation.xml | 11 +++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationFragment.kt index d9bee415559..0f307ecbff3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationFragment.kt @@ -6,7 +6,11 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.extensions.handleResult +import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment +import com.woocommerce.android.ui.blaze.creation.destination.BlazeCampaignCreationAdDestinationParametersFragment.Companion.BLAZE_DESTINATION_PARAMETERS_RESULT +import com.woocommerce.android.ui.blaze.creation.destination.BlazeCampaignCreationAdDestinationViewModel.NavigateToParametersScreen import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -34,11 +38,18 @@ class BlazeCampaignCreationAdDestinationFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { is MultiLiveEvent.Event.Exit -> findNavController().navigateUp() + is NavigateToParametersScreen -> { + val action = BlazeCampaignCreationAdDestinationFragmentDirections + .actionAdDestinationFragmentToAdDestinationParametersFragment(event.url) + findNavController().navigateSafely(action) + } } } } private fun handleResults() { - /* TODO */ + handleResult(BLAZE_DESTINATION_PARAMETERS_RESULT) { url -> + viewModel.onDestinationUrlChanged(url) + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt index 0d2776b3465..9ec00370ac2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt @@ -84,7 +84,8 @@ fun AdDestinationScreen( Divider() AdDestinationProperty( title = stringResource(id = R.string.blaze_campaign_edit_ad_destination_parameters_property_title), - value = viewState.parameters, + value = viewState.parameters + ?: stringResource(R.string.blaze_campaign_edit_ad_destination_empty_parameters_message), onPropertyTapped = onParametersPropertyTapped ) } @@ -221,10 +222,10 @@ fun PreviewAdDestinationScreen() { WooThemeWithBackground { AdDestinationScreen( viewState = ViewState( - parameters = "utm_source=woocommerce\nutm_medium=android\nutm_campaign=blaze", productUrl = "https://woocommerce.com/products/1", siteUrl = "https://woocommerce.com", - targetUrl = "https://woocommerce.com/products/12", + targetUrl = "https://woocommerce.com/products/12" + + "?utm_source=woocommerce_android&utm_medium=ad&utm_campaign=blaze", isUrlDialogVisible = true ), onBackPressed = {}, 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 abb123658de..c1c870a60e2 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 @@ -119,5 +119,16 @@ app:argType="string" /> + + + + From 1f715e579749f1a519a24991b74775979791f2a3 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 9 Feb 2024 12:43:04 +0100 Subject: [PATCH 012/119] Create boilerplate of the payment methods list screen --- ...BlazeCampaignPaymentMethodsListFragment.kt | 28 +++++++++++++++++++ .../BlazeCampaignPaymentMethodsListScreen.kt | 8 ++++++ ...lazeCampaignPaymentMethodsListViewModel.kt | 13 +++++++++ .../nav_graph_blaze_campaign_creation.xml | 11 ++++++++ 4 files changed, 60 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt new file mode 100644 index 00000000000..1730835aaba --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt @@ -0,0 +1,28 @@ +package com.woocommerce.android.ui.blaze.creation.payment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import com.woocommerce.android.ui.base.BaseFragment +import com.woocommerce.android.ui.compose.composeView +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import com.woocommerce.android.ui.main.AppBarStatus +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class BlazeCampaignPaymentMethodsListFragment : BaseFragment() { + override val activityAppBarStatus: AppBarStatus + get() = AppBarStatus.Hidden + + private val viewModel: BlazeCampaignPaymentMethodsListViewModel by viewModels() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return composeView { + WooThemeWithBackground { + BlazeCampaignPaymentMethodsListScreen(viewModel) + } + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt new file mode 100644 index 00000000000..e6919ab4fcd --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt @@ -0,0 +1,8 @@ +package com.woocommerce.android.ui.blaze.creation.payment + +import androidx.compose.runtime.Composable + +@Composable +fun BlazeCampaignPaymentMethodsListScreen(viewModel: BlazeCampaignPaymentMethodsListViewModel) { + TODO("Not yet implemented") +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt new file mode 100644 index 00000000000..744dc6fc4e1 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -0,0 +1,13 @@ +package com.woocommerce.android.ui.blaze.creation.payment + +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.viewmodel.ScopedViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( + savedStateHandle: SavedStateHandle +) : ScopedViewModel(savedStateHandle) { + +} 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 5b280bd1eaf..4e26747321e 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 @@ -143,5 +143,16 @@ + + + + From 7a473004b5c0522d2ed7251961322d0ff998f25a Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 9 Feb 2024 12:54:06 +0100 Subject: [PATCH 013/119] Add viewstate modeling --- ...lazeCampaignPaymentMethodsListViewModel.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index 744dc6fc4e1..414bd2ae196 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -1,8 +1,12 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.asLiveData +import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow import javax.inject.Inject @HiltViewModel @@ -10,4 +14,40 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( savedStateHandle: SavedStateHandle ) : ScopedViewModel(savedStateHandle) { + private val navArgs by savedStateHandle.navArgs() + + private val _viewState: MutableStateFlow = MutableStateFlow( + if (navArgs.paymentMethodsData.savedPaymentMethods.isEmpty()) { + addPaymentMethodWebView() + } else { + paymentMethodsListState() + } + ) + val viewState = _viewState.asLiveData() + + private fun paymentMethodsListState() = ViewState.PaymentMethodsList( + paymentMethods = navArgs.paymentMethodsData.savedPaymentMethods, + onPaymentMethodClicked = { /* TODO */ }, + onAddPaymentMethodClicked = { + _viewState.value = addPaymentMethodWebView() + } + ) + + private fun addPaymentMethodWebView() = ViewState.AddPaymentMethodWebView( + urls = navArgs.paymentMethodsData.paymentMethodUrls, + onUrlLoaded = { /* TODO */ } + ) + + sealed interface ViewState { + data class PaymentMethodsList( + val paymentMethods: List, + val onPaymentMethodClicked: (BlazeRepository.PaymentMethod) -> Unit, + val onAddPaymentMethodClicked: () -> Unit + ) : ViewState + + data class AddPaymentMethodWebView( + val urls: BlazeRepository.PaymentMethodUrls, + val onUrlLoaded: (String) -> Unit + ) : ViewState + } } From 609d578ca4b91104252b99f91f47ab94378b9eae Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 9 Feb 2024 13:07:41 +0100 Subject: [PATCH 014/119] Handle navigation to the payment methods list screen --- .../payment/BlazeCampaignPaymentSummaryFragment.kt | 9 +++++++++ .../payment/BlazeCampaignPaymentSummaryViewModel.kt | 10 +++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt index 0ff640f3e82..29ce188db9b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground @@ -36,6 +37,14 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { MultiLiveEvent.Event.Exit -> findNavController().navigateUp() + is BlazeCampaignPaymentSummaryViewModel.NavigateToPaymentsListScreen -> { + findNavController().navigateSafely( + BlazeCampaignPaymentSummaryFragmentDirections + .actionBlazeCampaignPaymentSummaryFragmentToBlazeCampaignPaymentMethodsListFragment( + event.paymentMethodsData + ) + ) + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index ab02eb172c8..e853425ba2c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -40,10 +40,10 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( paymentMethodState.value = PaymentMethodState.Loading launch { paymentMethodState.value = blazeRepository.fetchPaymentMethods().fold( - onSuccess = { + onSuccess = { paymentMethodsData -> PaymentMethodState.Success( - it, - onClick = { /* TODO */ } + paymentMethodsData = paymentMethodsData, + onClick = { triggerEvent(NavigateToPaymentsListScreen(paymentMethodsData)) } ) }, onFailure = { PaymentMethodState.Error { fetchPaymentMethodData() } } @@ -67,4 +67,8 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( data class Error(val onRetry: () -> Unit) : PaymentMethodState } + + data class NavigateToPaymentsListScreen( + val paymentMethodsData: PaymentMethodsData + ) : MultiLiveEvent.Event() } From e69ef04ffae984c472b366ee26fa6dca22d54467 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 9 Feb 2024 13:09:51 +0100 Subject: [PATCH 015/119] Disable the campaign creation button until a payment method is selected --- .../blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt | 2 ++ .../creation/payment/BlazeCampaignPaymentSummaryViewModel.kt | 1 + 2 files changed, 3 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt index e6e21fda6ea..e163a6d6bca 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt @@ -94,6 +94,8 @@ fun BlazeCampaignPaymentSummaryScreen( WCColoredButton( onClick = { /*TODO*/ }, text = stringResource(id = R.string.blaze_campaign_payment_summary_submit_campaign), + enabled = (state.paymentMethodState as? BlazeCampaignPaymentSummaryViewModel.PaymentMethodState.Success) + ?.isPaymentMethodSelected == true, modifier = Modifier .fillMaxWidth() .padding(horizontal = dimensionResource(id = R.dimen.major_100)) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index e853425ba2c..671bc4201d8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -63,6 +63,7 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( val onClick: () -> Unit ) : PaymentMethodState { val selectedPaymentMethod = paymentMethodsData.savedPaymentMethods.firstOrNull() + val isPaymentMethodSelected = selectedPaymentMethod != null } data class Error(val onRetry: () -> Unit) : PaymentMethodState From 9c0951c778e2cbe619d26191889c32b2cb1569c7 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 9 Feb 2024 16:24:51 +0100 Subject: [PATCH 016/119] Add UI of the payment list screen --- .../BlazeCampaignPaymentMethodsListScreen.kt | 273 ++++++++++++++++++ ...lazeCampaignPaymentMethodsListViewModel.kt | 23 +- WooCommerce/src/main/res/values/strings.xml | 7 +- 3 files changed, 294 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt index e6919ab4fcd..325a2ef0a61 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt @@ -1,8 +1,281 @@ package com.woocommerce.android.ui.blaze.creation.payment +import android.content.res.Configuration +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.ContentAlpha +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.woocommerce.android.R +import com.woocommerce.android.model.CreditCardType +import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethod +import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethodUrls +import com.woocommerce.android.ui.compose.component.Toolbar +import com.woocommerce.android.ui.compose.component.WCColoredButton +import com.woocommerce.android.ui.compose.component.WCTextButton +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground @Composable fun BlazeCampaignPaymentMethodsListScreen(viewModel: BlazeCampaignPaymentMethodsListViewModel) { + viewModel.viewState.observeAsState().value?.let { + BlazeCampaignPaymentMethodsListScreen( + viewState = it + ) + } +} + +@Composable +private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymentMethodsListViewModel.ViewState) { + Scaffold( + topBar = { + Toolbar( + title = stringResource(id = R.string.blaze_campaign_payment_list_screen_title), + onNavigationButtonClick = viewState.onDismiss, + navigationIcon = when (viewState) { + is BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList -> Icons.Default.ArrowBack + is BlazeCampaignPaymentMethodsListViewModel.ViewState.AddPaymentMethodWebView -> Icons.Default.Clear + } + ) + }, + backgroundColor = MaterialTheme.colors.surface + ) { paddingValues -> + when (viewState) { + is BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList -> { + PaymentMethodsList( + paymentMethods = viewState.paymentMethods, + onPaymentMethodClicked = viewState.onPaymentMethodClicked, + onAddPaymentMethodClicked = viewState.onAddPaymentMethodClicked, + modifier = Modifier.padding(paddingValues) + ) + } + + is BlazeCampaignPaymentMethodsListViewModel.ViewState.AddPaymentMethodWebView -> { + AddPaymentMethodWebView( + urls = viewState.urls, + onUrlLoaded = viewState.onUrlLoaded, + modifier = Modifier.padding(paddingValues) + ) + } + } + } +} + +@Suppress("UNUSED_PARAMETER") +@Composable +private fun PaymentMethodsList( + paymentMethods: List, + onPaymentMethodClicked: (PaymentMethod) -> Unit, + onAddPaymentMethodClicked: () -> Unit, + modifier: Modifier +) { + Column(modifier) { + PaymentMethodsHeader(Modifier.fillMaxWidth()) + if (paymentMethods.isEmpty()) { + EmptyPaymentMethodsView( + onAddPaymentMethodClicked = onAddPaymentMethodClicked, + modifier = modifier + .fillMaxWidth() + .weight(1f) + ) + } else { + PaymentMethodsListView( + paymentMethods = paymentMethods, + onPaymentMethodClicked = onPaymentMethodClicked, + onAddPaymentMethodClicked = onAddPaymentMethodClicked, + modifier = modifier + .fillMaxWidth() + .weight(1f) + ) + } + } +} + +@Composable +private fun PaymentMethodsHeader(modifier: Modifier = Modifier) { + Row(modifier.padding(dimensionResource(id = R.dimen.major_100))) { + Icon( + imageVector = Icons.Outlined.VerifiedUser, + tint = MaterialTheme.colors.primary, + contentDescription = null + ) + Spacer(modifier = Modifier.padding(dimensionResource(id = R.dimen.minor_100))) + Text(text = stringResource(id = R.string.blaze_campaign_payment_list_header_text)) + } +} + +@Composable +private fun PaymentMethodsListView( + paymentMethods: List, + onPaymentMethodClicked: (PaymentMethod) -> Unit, + onAddPaymentMethodClicked: () -> Unit, + modifier: Modifier +) { + LazyColumn(modifier = modifier) { + items(paymentMethods) { paymentMethod -> + Column { + PaymentMethodItem( + paymentMethod = paymentMethod, + onPaymentMethodClicked = { onPaymentMethodClicked(paymentMethod) }, + isSelected = true + ) + } + Divider() + } + + item { + Text( + text = stringResource(id = R.string.blaze_campaign_payment_list_hint, "username", "email"), + style = MaterialTheme.typography.caption, + color = MaterialTheme.colors.onSurface.copy( + alpha = ContentAlpha.medium + ), + modifier = Modifier.padding(dimensionResource(id = R.dimen.major_100)) + ) + } + + item { + WCTextButton( + onClick = onAddPaymentMethodClicked, + icon = Icons.Default.Add, + text = stringResource(id = R.string.blaze_campaign_payment_list_add_new_payment_method_button) + ) + } + } +} + +@Composable +private fun PaymentMethodItem( + paymentMethod: PaymentMethod, + onPaymentMethodClicked: () -> Unit, + isSelected: Boolean, + modifier: Modifier = Modifier +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .clickable(onClick = onPaymentMethodClicked) + .padding(dimensionResource(id = R.dimen.major_100)) + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = paymentMethod.name, + style = MaterialTheme.typography.subtitle1 + ) + paymentMethod.subtitle?.let { + Text( + text = it, + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onSurface.copy( + alpha = ContentAlpha.medium + ) + ) + } + } + + if (isSelected) { + Icon( + imageVector = Icons.Default.Check, + tint = MaterialTheme.colors.primary, + contentDescription = null + ) + } + } +} + +@Composable +private fun EmptyPaymentMethodsView( + onAddPaymentMethodClicked: () -> Unit, + modifier: Modifier +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier.padding( + horizontal = dimensionResource(id = R.dimen.major_100), + vertical = dimensionResource(id = R.dimen.major_200) + ) + ) { + Text(text = stringResource(id = R.string.blaze_campaign_payment_list_empty_state_text)) + Spacer(modifier = Modifier.padding(dimensionResource(id = R.dimen.major_100))) + WCColoredButton( + onClick = onAddPaymentMethodClicked, + text = stringResource(id = R.string.blaze_campaign_payment_list_add_payment_method_button) + ) + } +} + +@Suppress("UNUSED_PARAMETER") +@Composable +fun AddPaymentMethodWebView( + urls: PaymentMethodUrls, + onUrlLoaded: (String) -> Unit, + modifier: Modifier +) { TODO("Not yet implemented") } + +@Preview(name = "dark", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(name = "light", uiMode = Configuration.UI_MODE_NIGHT_NO) +@Composable +private fun EmptyPaymentMethodsListPreview() { + WooThemeWithBackground { + BlazeCampaignPaymentMethodsListScreen( + viewState = BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList( + paymentMethods = emptyList(), + onPaymentMethodClicked = {}, + onAddPaymentMethodClicked = {}, + onDismiss = {} + ) + ) + } +} + +@Preview(name = "dark", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(name = "light", uiMode = Configuration.UI_MODE_NIGHT_NO) +@Composable +private fun PaymentMethodsListPreview() { + WooThemeWithBackground { + BlazeCampaignPaymentMethodsListScreen( + viewState = BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList( + paymentMethods = listOf( + PaymentMethod( + id = "1", + name = "Visa", + subtitle = "John Doe", + type = PaymentMethod.PaymentMethodType.CreditCard(CreditCardType.VISA) + ), + PaymentMethod( + id = "2", + name = "MasterCard", + subtitle = "John Doe", + type = PaymentMethod.PaymentMethodType.CreditCard(CreditCardType.MASTERCARD) + ) + ), + onPaymentMethodClicked = {}, + onAddPaymentMethodClicked = {}, + onDismiss = {} + ) + ) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index 414bd2ae196..9a4b413bd10 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import com.woocommerce.android.ui.blaze.BlazeRepository +import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel @@ -16,7 +17,7 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( private val navArgs by savedStateHandle.navArgs() - private val _viewState: MutableStateFlow = MutableStateFlow( + private val _viewState = MutableStateFlow( if (navArgs.paymentMethodsData.savedPaymentMethods.isEmpty()) { addPaymentMethodWebView() } else { @@ -25,29 +26,35 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( ) val viewState = _viewState.asLiveData() - private fun paymentMethodsListState() = ViewState.PaymentMethodsList( + private fun paymentMethodsListState(): ViewState = ViewState.PaymentMethodsList( paymentMethods = navArgs.paymentMethodsData.savedPaymentMethods, onPaymentMethodClicked = { /* TODO */ }, onAddPaymentMethodClicked = { _viewState.value = addPaymentMethodWebView() - } + }, + onDismiss = { triggerEvent(MultiLiveEvent.Event.Exit) } ) - private fun addPaymentMethodWebView() = ViewState.AddPaymentMethodWebView( - urls = navArgs.paymentMethodsData.paymentMethodUrls, - onUrlLoaded = { /* TODO */ } + private fun addPaymentMethodWebView(): ViewState = ViewState.AddPaymentMethodWebView( + urls = navArgs.paymentMethodsData.addPaymentMethodUrls, + onUrlLoaded = { /* TODO */ }, + onDismiss = { _viewState.value = paymentMethodsListState() } ) sealed interface ViewState { + val onDismiss: () -> Unit + data class PaymentMethodsList( val paymentMethods: List, val onPaymentMethodClicked: (BlazeRepository.PaymentMethod) -> Unit, - val onAddPaymentMethodClicked: () -> Unit + val onAddPaymentMethodClicked: () -> Unit, + override val onDismiss: () -> Unit ) : ViewState data class AddPaymentMethodWebView( val urls: BlazeRepository.PaymentMethodUrls, - val onUrlLoaded: (String) -> Unit + val onUrlLoaded: (String) -> Unit, + override val onDismiss: () -> Unit ) : ViewState } } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 4ccec7dfe2e..8f12f75492c 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3884,7 +3884,12 @@ Loading payment methods failed, please retry by clicking here! Submit campaign By clicking \"Submit campaign\" you agree to the <a href=\'termsOfService\'><u>Terms of Service</u></a> and <a href=\'advertisingPolicy\'><u>Advertising Policy</u></a>, and authorize your payment method to be charged for the budget and duration you chose. <a href=\'learnMore\'><u>Learn more</u></a> about how budgets and payments for Promoted Posts work. - + Payment method + Please add a new payment method + Add credit card + All transactions are secure and encrypted + Credits cards are retrieved from the following WordPress.com account: %1$s <%2$s> + Add new card From 244e05afffed57fdc014241c305e2d8d388a2709 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 9 Feb 2024 16:49:44 +0100 Subject: [PATCH 017/119] Initial handling of payment method selection --- .../BlazeCampaignPaymentMethodsListScreen.kt | 41 +++++++++++-------- ...lazeCampaignPaymentMethodsListViewModel.kt | 4 ++ .../BlazeCampaignPaymentSummaryFragment.kt | 3 +- .../BlazeCampaignPaymentSummaryViewModel.kt | 13 +++++- .../nav_graph_blaze_campaign_creation.xml | 4 ++ 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt index 325a2ef0a61..40cb622411a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource @@ -65,6 +66,7 @@ private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymen is BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList -> { PaymentMethodsList( paymentMethods = viewState.paymentMethods, + selectedPaymentMethod = viewState.selectedPaymentMethod!!, onPaymentMethodClicked = viewState.onPaymentMethodClicked, onAddPaymentMethodClicked = viewState.onAddPaymentMethodClicked, modifier = Modifier.padding(paddingValues) @@ -82,10 +84,10 @@ private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymen } } -@Suppress("UNUSED_PARAMETER") @Composable private fun PaymentMethodsList( paymentMethods: List, + selectedPaymentMethod: PaymentMethod, onPaymentMethodClicked: (PaymentMethod) -> Unit, onAddPaymentMethodClicked: () -> Unit, modifier: Modifier @@ -102,6 +104,7 @@ private fun PaymentMethodsList( } else { PaymentMethodsListView( paymentMethods = paymentMethods, + selectedPaymentMethod = selectedPaymentMethod, onPaymentMethodClicked = onPaymentMethodClicked, onAddPaymentMethodClicked = onAddPaymentMethodClicked, modifier = modifier @@ -128,6 +131,7 @@ private fun PaymentMethodsHeader(modifier: Modifier = Modifier) { @Composable private fun PaymentMethodsListView( paymentMethods: List, + selectedPaymentMethod: PaymentMethod, onPaymentMethodClicked: (PaymentMethod) -> Unit, onAddPaymentMethodClicked: () -> Unit, modifier: Modifier @@ -138,7 +142,7 @@ private fun PaymentMethodsListView( PaymentMethodItem( paymentMethod = paymentMethod, onPaymentMethodClicked = { onPaymentMethodClicked(paymentMethod) }, - isSelected = true + isSelected = paymentMethod == selectedPaymentMethod ) } Divider() @@ -243,6 +247,7 @@ private fun EmptyPaymentMethodsListPreview() { BlazeCampaignPaymentMethodsListScreen( viewState = BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList( paymentMethods = emptyList(), + selectedPaymentMethod = null, onPaymentMethodClicked = {}, onAddPaymentMethodClicked = {}, onDismiss = {} @@ -255,23 +260,27 @@ private fun EmptyPaymentMethodsListPreview() { @Preview(name = "light", uiMode = Configuration.UI_MODE_NIGHT_NO) @Composable private fun PaymentMethodsListPreview() { + val paymentMethods = remember { + listOf( + PaymentMethod( + id = "1", + name = "Visa", + subtitle = "John Doe", + type = PaymentMethod.PaymentMethodType.CreditCard(CreditCardType.VISA) + ), + PaymentMethod( + id = "2", + name = "MasterCard", + subtitle = "John Doe", + type = PaymentMethod.PaymentMethodType.CreditCard(CreditCardType.MASTERCARD) + ) + ) + } WooThemeWithBackground { BlazeCampaignPaymentMethodsListScreen( viewState = BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList( - paymentMethods = listOf( - PaymentMethod( - id = "1", - name = "Visa", - subtitle = "John Doe", - type = PaymentMethod.PaymentMethodType.CreditCard(CreditCardType.VISA) - ), - PaymentMethod( - id = "2", - name = "MasterCard", - subtitle = "John Doe", - type = PaymentMethod.PaymentMethodType.CreditCard(CreditCardType.MASTERCARD) - ) - ), + paymentMethods = paymentMethods, + selectedPaymentMethod = paymentMethods.first(), onPaymentMethodClicked = {}, onAddPaymentMethodClicked = {}, onDismiss = {} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index 9a4b413bd10..802d42b0385 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -28,6 +28,9 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( private fun paymentMethodsListState(): ViewState = ViewState.PaymentMethodsList( paymentMethods = navArgs.paymentMethodsData.savedPaymentMethods, + selectedPaymentMethod = navArgs.paymentMethodsData.savedPaymentMethods.firstOrNull { + it.id == navArgs.selectedPaymentMethodId + }, onPaymentMethodClicked = { /* TODO */ }, onAddPaymentMethodClicked = { _viewState.value = addPaymentMethodWebView() @@ -46,6 +49,7 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( data class PaymentMethodsList( val paymentMethods: List, + val selectedPaymentMethod: BlazeRepository.PaymentMethod?, val onPaymentMethodClicked: (BlazeRepository.PaymentMethod) -> Unit, val onAddPaymentMethodClicked: () -> Unit, override val onDismiss: () -> Unit diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt index 29ce188db9b..ee4520fd605 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt @@ -41,7 +41,8 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { findNavController().navigateSafely( BlazeCampaignPaymentSummaryFragmentDirections .actionBlazeCampaignPaymentSummaryFragmentToBlazeCampaignPaymentMethodsListFragment( - event.paymentMethodsData + paymentMethodsData = event.paymentMethodsData, + selectedPaymentMethodId = event.selectedPaymentMethodId ) ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index 671bc4201d8..5d96c6dc3fc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -43,7 +43,15 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( onSuccess = { paymentMethodsData -> PaymentMethodState.Success( paymentMethodsData = paymentMethodsData, - onClick = { triggerEvent(NavigateToPaymentsListScreen(paymentMethodsData)) } + onClick = { + triggerEvent( + NavigateToPaymentsListScreen( + paymentMethodsData = paymentMethodsData, + // TODO we should handle selecting different payment methods + selectedPaymentMethodId = paymentMethodsData.savedPaymentMethods.firstOrNull()?.id + ) + ) + } ) }, onFailure = { PaymentMethodState.Error { fetchPaymentMethodData() } } @@ -70,6 +78,7 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( } data class NavigateToPaymentsListScreen( - val paymentMethodsData: PaymentMethodsData + val paymentMethodsData: PaymentMethodsData, + val selectedPaymentMethodId: String? ) : MultiLiveEvent.Event() } 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 4e26747321e..5dcd38fef9e 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 @@ -154,5 +154,9 @@ + From f3da98359677e72eba0899b8125576dad2664195 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 9 Feb 2024 18:07:09 +0100 Subject: [PATCH 018/119] Handle payment method selection --- ...BlazeCampaignPaymentMethodsListFragment.kt | 23 ++++++++ .../BlazeCampaignPaymentMethodsListScreen.kt | 18 +++--- ...lazeCampaignPaymentMethodsListViewModel.kt | 4 +- .../BlazeCampaignPaymentSummaryFragment.kt | 8 +++ .../BlazeCampaignPaymentSummaryViewModel.kt | 55 +++++++++++++++---- 5 files changed, 90 insertions(+), 18 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt index 1730835aaba..3925094c17b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt @@ -5,14 +5,21 @@ 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.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus +import com.woocommerce.android.viewmodel.MultiLiveEvent import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class BlazeCampaignPaymentMethodsListFragment : BaseFragment() { + companion object { + const val SELECTED_PAYMENT_METHOD_KEY = "selectedPaymentMethodId" + } + override val activityAppBarStatus: AppBarStatus get() = AppBarStatus.Hidden @@ -25,4 +32,20 @@ class BlazeCampaignPaymentMethodsListFragment : BaseFragment() { } } } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + handleResults() + } + + private fun handleResults() { + viewModel.event.observe(viewLifecycleOwner) { event -> + when (event) { + MultiLiveEvent.Event.Exit -> findNavController().navigateUp() + is MultiLiveEvent.Event.ExitWithResult<*> -> navigateBackWithResult( + key = SELECTED_PAYMENT_METHOD_KEY, + result = event.data + ) + } + } + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt index 40cb622411a..c8c45c75b12 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt @@ -66,7 +66,7 @@ private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymen is BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList -> { PaymentMethodsList( paymentMethods = viewState.paymentMethods, - selectedPaymentMethod = viewState.selectedPaymentMethod!!, + selectedPaymentMethod = viewState.selectedPaymentMethod, onPaymentMethodClicked = viewState.onPaymentMethodClicked, onAddPaymentMethodClicked = viewState.onAddPaymentMethodClicked, modifier = Modifier.padding(paddingValues) @@ -87,7 +87,7 @@ private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymen @Composable private fun PaymentMethodsList( paymentMethods: List, - selectedPaymentMethod: PaymentMethod, + selectedPaymentMethod: PaymentMethod?, onPaymentMethodClicked: (PaymentMethod) -> Unit, onAddPaymentMethodClicked: () -> Unit, modifier: Modifier @@ -104,7 +104,7 @@ private fun PaymentMethodsList( } else { PaymentMethodsListView( paymentMethods = paymentMethods, - selectedPaymentMethod = selectedPaymentMethod, + selectedPaymentMethod = selectedPaymentMethod!!, onPaymentMethodClicked = onPaymentMethodClicked, onAddPaymentMethodClicked = onAddPaymentMethodClicked, modifier = modifier @@ -265,14 +265,18 @@ private fun PaymentMethodsListPreview() { PaymentMethod( id = "1", name = "Visa", - subtitle = "John Doe", - type = PaymentMethod.PaymentMethodType.CreditCard(CreditCardType.VISA) + info = PaymentMethod.PaymentMethodInfo.CreditCard( + CreditCardType.VISA, + "John Doe" + ) ), PaymentMethod( id = "2", name = "MasterCard", - subtitle = "John Doe", - type = PaymentMethod.PaymentMethodType.CreditCard(CreditCardType.MASTERCARD) + info = PaymentMethod.PaymentMethodInfo.CreditCard( + CreditCardType.MASTERCARD, + "John Doe" + ) ) ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index 802d42b0385..84877ab6db1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -31,7 +31,9 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( selectedPaymentMethod = navArgs.paymentMethodsData.savedPaymentMethods.firstOrNull { it.id == navArgs.selectedPaymentMethodId }, - onPaymentMethodClicked = { /* TODO */ }, + onPaymentMethodClicked = { + triggerEvent(MultiLiveEvent.Event.ExitWithResult(it.id)) + }, onAddPaymentMethodClicked = { _viewState.value = addPaymentMethodWebView() }, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt index ee4520fd605..62c73453f07 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.compose.composeView @@ -31,6 +32,7 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { handleEvents() + handleResults() } private fun handleEvents() { @@ -49,4 +51,10 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { } } } + + private fun handleResults() { + handleResult(BlazeCampaignPaymentMethodsListFragment.SELECTED_PAYMENT_METHOD_KEY) { + viewModel.onPaymentMethodSelected(it) + } + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index 5d96c6dc3fc..d8429f1fe69 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -2,13 +2,15 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethodsData import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ScopedViewModel +import com.woocommerce.android.viewmodel.getNullableStateFlow import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import javax.inject.Inject @@ -19,12 +21,26 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( ) : ScopedViewModel(savedStateHandle) { private val navArgs = BlazeCampaignPaymentSummaryFragmentArgs.fromSavedStateHandle(savedStateHandle) + private val selectedPaymentMethodId = savedStateHandle.getNullableStateFlow( + scope = viewModelScope, + initialValue = null, + clazz = String::class.java, + key = "selectedPaymentMethodId" + ) private val paymentMethodState = MutableStateFlow(PaymentMethodState.Loading) - val viewState = paymentMethodState.map { + val viewState = combine( + selectedPaymentMethodId, + paymentMethodState + ) { selectedPaymentMethodId, paymentMethodState -> ViewState( budget = navArgs.budget, - paymentMethodState = it + paymentMethodState = paymentMethodState.let { + when (it) { + is PaymentMethodState.Success -> it.copy(selectedPaymentMethodId = selectedPaymentMethodId) + else -> it + } + } ) }.asLiveData() @@ -36,25 +52,41 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( triggerEvent(MultiLiveEvent.Event.Exit) } + fun onPaymentMethodSelected(paymentMethodId: String) { + selectedPaymentMethodId.value = paymentMethodId + + val paymentMethodState = paymentMethodState.value + if (paymentMethodState is PaymentMethodState.Success && + !paymentMethodState.paymentMethodsData.savedPaymentMethods.any { it.id == paymentMethodId } + ) { + fetchPaymentMethodData() + } + } + private fun fetchPaymentMethodData() { paymentMethodState.value = PaymentMethodState.Loading launch { - paymentMethodState.value = blazeRepository.fetchPaymentMethods().fold( + blazeRepository.fetchPaymentMethods().fold( onSuccess = { paymentMethodsData -> - PaymentMethodState.Success( + if (selectedPaymentMethodId.value == null) { + selectedPaymentMethodId.value = paymentMethodsData.savedPaymentMethods.firstOrNull()?.id + } + + paymentMethodState.value = PaymentMethodState.Success( paymentMethodsData = paymentMethodsData, onClick = { triggerEvent( NavigateToPaymentsListScreen( paymentMethodsData = paymentMethodsData, - // TODO we should handle selecting different payment methods - selectedPaymentMethodId = paymentMethodsData.savedPaymentMethods.firstOrNull()?.id + selectedPaymentMethodId = selectedPaymentMethodId.value ) ) } ) }, - onFailure = { PaymentMethodState.Error { fetchPaymentMethodData() } } + onFailure = { + paymentMethodState.value = PaymentMethodState.Error { fetchPaymentMethodData() } + } ) } } @@ -67,10 +99,13 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( sealed interface PaymentMethodState { data object Loading : PaymentMethodState data class Success( - private val paymentMethodsData: PaymentMethodsData, + val paymentMethodsData: PaymentMethodsData, + private val selectedPaymentMethodId: String? = null, val onClick: () -> Unit ) : PaymentMethodState { - val selectedPaymentMethod = paymentMethodsData.savedPaymentMethods.firstOrNull() + val selectedPaymentMethod = selectedPaymentMethodId?.let { id -> + paymentMethodsData.savedPaymentMethods.find { it.id == id } + } ?: paymentMethodsData.savedPaymentMethods.firstOrNull() val isPaymentMethodSelected = selectedPaymentMethod != null } From 1b7093fb8e4f881a45b1be24ea8191f5aea6cd06 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 9 Feb 2024 21:17:20 +0100 Subject: [PATCH 019/119] Add URL manipulation util extension functions --- .../com/woocommerce/android/util/UrlUtils.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt index 5a501943af9..5a802e928c7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.util import android.content.Context import com.woocommerce.android.AppUrls +import com.woocommerce.android.extensions.appendWithIfNotEmpty import dagger.Reusable import org.wordpress.android.fluxc.network.discovery.DiscoveryUtils import org.wordpress.android.util.LanguageUtils @@ -33,3 +34,22 @@ class UrlUtils @Inject constructor( } } } + +fun String.getBaseUrl(): String { + return (split("?").getOrNull(0) ?: this).trimEnd('/') +} + +fun String.parseParameters(): Map { + val parameters = split("?").getOrNull(1) ?: return emptyMap() + return parameters.split("&").filter { it.contains("=") }.associate { + val (key, value) = it.split("=") + key to value + } +} + +fun Map.joinToUrl(baseUrl: String) = buildString { + append(baseUrl) + appendWithIfNotEmpty(joinToString(), "?") +} + +fun Map.joinToString() = entries.joinToString("&") { (key, value) -> "$key=$value" } From dd5fa4b805a6f56b7e542ae167e309a8b7809ea7 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 9 Feb 2024 21:18:22 +0100 Subject: [PATCH 020/119] Update state change management when parameters are updated --- ...zeCampaignCreationAdDestinationFragment.kt | 2 +- ...gnCreationAdDestinationParametersScreen.kt | 60 ++++++++++--------- ...reationAdDestinationParametersViewModel.kt | 32 +++------- ...lazeCampaignCreationAdDestinationScreen.kt | 25 ++++---- ...eCampaignCreationAdDestinationViewModel.kt | 56 ++++++++++++----- 5 files changed, 95 insertions(+), 80 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationFragment.kt index 0f307ecbff3..bca35bd5d89 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationFragment.kt @@ -49,7 +49,7 @@ class BlazeCampaignCreationAdDestinationFragment : BaseFragment() { private fun handleResults() { handleResult(BLAZE_DESTINATION_PARAMETERS_RESULT) { url -> - viewModel.onDestinationUrlChanged(url) + viewModel.onTargetUrlUpdated(url) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt index e5325369148..7dfc9e9cf38 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt @@ -77,7 +77,7 @@ fun AdDestinationParametersScreen( .padding(paddingValues) .fillMaxSize(), ) { - item { + item(key = "header") { WCTextButton( modifier = Modifier .fillMaxWidth() @@ -90,7 +90,7 @@ fun AdDestinationParametersScreen( itemsIndexed( items = viewState.parameters.entries.toList(), - key = { _, item -> item.key } + key = { _, item -> "key${item.key}" } ) { index, (key, value) -> Column( modifier = Modifier @@ -142,34 +142,40 @@ fun AdDestinationParametersScreen( } } - item { - Text( + item(key = "footer") { + Column( modifier = Modifier - .padding( - start = dimensionResource(id = R.dimen.major_100), - end = dimensionResource(id = R.dimen.major_100), - top = dimensionResource(id = R.dimen.major_100), - bottom = dimensionResource(id = R.dimen.minor_100) + .animateItemPlacement() + .fillMaxWidth() + ) { + Text( + modifier = Modifier + .padding( + start = dimensionResource(id = R.dimen.major_100), + end = dimensionResource(id = R.dimen.major_100), + top = dimensionResource(id = R.dimen.major_100), + bottom = dimensionResource(id = R.dimen.minor_100) + ), + text = stringResource( + R.string.blaze_campaign_edit_ad_characters_remaining, + viewState.charactersRemaining ), - text = stringResource( - R.string.blaze_campaign_edit_ad_characters_remaining, - viewState.charactersRemaining - ), - style = MaterialTheme.typography.caption, - color = colorResource(id = R.color.color_on_surface_medium) - ) - Text( - modifier = Modifier - .padding( - horizontal = dimensionResource(id = R.dimen.major_100), + style = MaterialTheme.typography.caption, + color = colorResource(id = R.color.color_on_surface_medium) + ) + Text( + modifier = Modifier + .padding( + horizontal = dimensionResource(id = R.dimen.major_100), + ), + text = stringResource( + R.string.blaze_campaign_edit_ad_destination_destination_with_parameters, + viewState.url ), - text = stringResource( - R.string.blaze_campaign_edit_ad_destination_destination_with_parameters, - viewState.url - ), - style = MaterialTheme.typography.caption, - color = colorResource(id = R.color.color_on_surface_medium) - ) + style = MaterialTheme.typography.caption, + color = colorResource(id = R.color.color_on_surface_medium) + ) + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt index 2de4dd43771..e4f9e41680b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt @@ -2,6 +2,9 @@ package com.woocommerce.android.ui.blaze.creation.destination import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData +import com.woocommerce.android.util.getBaseUrl +import com.woocommerce.android.util.joinToUrl +import com.woocommerce.android.util.parseParameters import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs @@ -22,8 +25,8 @@ class BlazeCampaignCreationAdDestinationParametersViewModel @Inject constructor( private val _viewState = MutableStateFlow( ViewState( - baseUrl = getBaseUrl(navArgs.url), - parameters = parseParameters(navArgs.url) + baseUrl = navArgs.url.getBaseUrl(), + parameters = navArgs.url.parseParameters() ) ) @@ -48,31 +51,14 @@ class BlazeCampaignCreationAdDestinationParametersViewModel @Inject constructor( } } - private fun getBaseUrl(url: String): String { - return url.split("?").getOrNull(0) ?: url - } - - private fun parseParameters(url: String): Map { - val parameters = url.split("?").getOrNull(1) ?: return emptyMap() - return parameters.split("&").associate { - val (key, value) = it.split("=") - key to value - } - } - data class ViewState( private val baseUrl: String, val parameters: Map ) { - val url: String - get() = buildString { - append(baseUrl) - append("?") - append(parameters.entries.joinToString("&") { (key, value) -> - "$key=$value" - }) - } - + val url by lazy { + parameters.joinToUrl(baseUrl) + } + val charactersRemaining: Int get() = MAX_CHARACTERS - url.length } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt index 9ec00370ac2..c0c1933770d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationScreen.kt @@ -78,14 +78,13 @@ fun AdDestinationScreen( ) { AdDestinationProperty( title = stringResource(id = R.string.blaze_campaign_edit_ad_destination_url_property_title), - value = viewState.targetUrl, + value = viewState.destinationUrl, onPropertyTapped = onUrlPropertyTapped ) Divider() AdDestinationProperty( title = stringResource(id = R.string.blaze_campaign_edit_ad_destination_parameters_property_title), - value = viewState.parameters - ?: stringResource(R.string.blaze_campaign_edit_ad_destination_empty_parameters_message), + value = viewState.parameters, onPropertyTapped = onParametersPropertyTapped ) } @@ -93,7 +92,7 @@ fun AdDestinationScreen( if (viewState.isUrlDialogVisible) { AdDestinationUrlDialog( viewState, - onDismissed = { onDestinationUrlChanged(viewState.targetUrl) }, + onDismissed = { onDestinationUrlChanged(viewState.destinationUrl) }, onSaveTapped = onDestinationUrlChanged ) } @@ -143,30 +142,30 @@ fun AdDestinationUrlDialog( style = MaterialTheme.typography.h6 ) - var targetUrl by rememberSaveable { - mutableStateOf(viewState.targetUrl) + var destinationUrl by rememberSaveable { + mutableStateOf(viewState.destinationUrl) } UrlOption( url = viewState.productUrl, - targetUrl = targetUrl, + targetUrl = destinationUrl, title = R.string.blaze_campaign_edit_ad_destination_product_url_option ) { - targetUrl = viewState.productUrl + destinationUrl = viewState.productUrl } UrlOption( url = viewState.siteUrl, - targetUrl = targetUrl, + targetUrl = destinationUrl, title = R.string.blaze_campaign_edit_ad_destination_site_url_option ) { - targetUrl = viewState.siteUrl + destinationUrl = viewState.siteUrl } DialogButtonsRowLayout( confirmButton = { WCTextButton(onClick = { - onSaveTapped(targetUrl) + onSaveTapped(destinationUrl) }) { Text(text = stringResource(id = R.string.save)) } @@ -224,8 +223,8 @@ fun PreviewAdDestinationScreen() { viewState = ViewState( productUrl = "https://woocommerce.com/products/1", siteUrl = "https://woocommerce.com", - targetUrl = "https://woocommerce.com/products/12" + - "?utm_source=woocommerce_android&utm_medium=ad&utm_campaign=blaze", + destinationUrl = "https://woocommerce.com/products/12", + parameters = "utm_source=woocommerce_android\nutm_medium=ad\nutm_campaign=blaze", isUrlDialogVisible = true ), onBackPressed = {}, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt index 9e6ced36b7d..8338217d32a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt @@ -2,10 +2,13 @@ package com.woocommerce.android.ui.blaze.creation.destination import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData +import com.woocommerce.android.R import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.products.ProductDetailRepository +import com.woocommerce.android.util.getBaseUrl import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit +import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel @@ -17,7 +20,8 @@ import javax.inject.Inject class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( savedStateHandle: SavedStateHandle, selectedSite: SelectedSite, - productDetailRepository: ProductDetailRepository + productDetailRepository: ProductDetailRepository, + private val resourceProvider: ResourceProvider ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationAdDestinationFragmentArgs by savedStateHandle.navArgs() private val productUrl = requireNotNull(productDetailRepository.getProduct(navArgs.productId)) @@ -25,9 +29,10 @@ class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( private val _viewState = MutableStateFlow( ViewState( - productUrl = productUrl, - siteUrl = selectedSite.get().url, - targetUrl = navArgs.targetUrl + "?utm_source=woocommerce_android&utm_medium=ad&utm_campaign=blaze", + productUrl = productUrl.trim('/'), + siteUrl = selectedSite.get().url.trim('/'), + destinationUrl = navArgs.targetUrl.getBaseUrl(), + parameters = getParameters(navArgs.targetUrl), isUrlDialogVisible = false ) ) @@ -43,30 +48,49 @@ class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( } fun onParameterPropertyTapped() { - triggerEvent(NavigateToParametersScreen(_viewState.value.targetUrl)) + triggerEvent( + NavigateToParametersScreen(getTargetUrl(_viewState.value.destinationUrl, _viewState.value.parameters)) + ) + } + + fun onTargetUrlUpdated(targetUrl: String) { + _viewState.update { + it.copy( + destinationUrl = targetUrl.getBaseUrl(), + parameters = getParameters(targetUrl), + ) + } } fun onDestinationUrlChanged(destinationUrl: String) { _viewState.value = _viewState.value.copy( - targetUrl = destinationUrl, + destinationUrl = destinationUrl, isUrlDialogVisible = false ) } + private fun getParameters(url: String): String { + return url.split("?") + .getOrNull(1) + ?.replace("&", "\n") + ?: return resourceProvider.getString(R.string.blaze_campaign_edit_ad_destination_empty_parameters_message) + } + + private fun getTargetUrl(baseUrl: String, parameters: String): String { + return if (parameters.isEmpty()) { + baseUrl + } else { + "$baseUrl?${parameters.replace("\n", "&")}" + } + } + data class ViewState( val productUrl: String, val siteUrl: String, - val targetUrl: String, + val destinationUrl: String, + val parameters: String, val isUrlDialogVisible: Boolean - ) { - val parameters: String? - get() = getParameters(targetUrl) - - private fun getParameters(url: String): String? { - val parameters = url.split("?").getOrNull(1) ?: return null - return parameters.replace("&", "\n") - } - } + ) data class NavigateToParametersScreen(val url: String) : MultiLiveEvent.Event() } From 97cc4f075db0fe1a3b93a590e893727b1a26fcc9 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Fri, 9 Feb 2024 21:19:54 +0100 Subject: [PATCH 021/119] Fix ktlint issues --- ...mpaignCreationAdDestinationParametersScreen.kt | 15 ++++++++------- ...ignCreationAdDestinationParametersViewModel.kt | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt index 7dfc9e9cf38..6779a59275e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersScreen.kt @@ -97,13 +97,14 @@ fun AdDestinationParametersScreen( .animateItemPlacement() .fillMaxWidth() ) { - Row(modifier = Modifier - .clickable { onParameterTapped(key) } - .padding( - start = dimensionResource(id = R.dimen.major_100), - top = dimensionResource(id = R.dimen.minor_100), - bottom = dimensionResource(id = R.dimen.minor_100) - ), + Row( + modifier = Modifier + .clickable { onParameterTapped(key) } + .padding( + start = dimensionResource(id = R.dimen.major_100), + top = dimensionResource(id = R.dimen.minor_100), + bottom = dimensionResource(id = R.dimen.minor_100) + ), verticalAlignment = CenterVertically ) { Column(modifier = Modifier.weight(1f)) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt index e4f9e41680b..3f2af0794f0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt @@ -42,7 +42,7 @@ class BlazeCampaignCreationAdDestinationParametersViewModel @Inject constructor( @Suppress("UNUSED_PARAMETER") fun onParameterTapped(key: String) { - /* TODO: */ + /* TODO */ } fun onDeleteParameterTapped(key: String) { From 43fc7fdc4a7214b2408f20aa1b054b12c49f593f Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Fri, 9 Feb 2024 19:37:25 -0500 Subject: [PATCH 022/119] reworked the layout to better support all screen sizes. --- .../layout/view_expandable_notice_card.xml | 82 ++++++++----------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/WooCommerce/src/main/res/layout/view_expandable_notice_card.xml b/WooCommerce/src/main/res/layout/view_expandable_notice_card.xml index 6a318177d64..99d4fc7e176 100644 --- a/WooCommerce/src/main/res/layout/view_expandable_notice_card.xml +++ b/WooCommerce/src/main/res/layout/view_expandable_notice_card.xml @@ -24,58 +24,47 @@ tools:textOff="@string/product_wip_title" tools:textOn="@string/product_wip_title_m5" /> - - - - - - + tools:text="@string/error_chooser_photo" /> - - + + - + + - From 52ec2c24f7b2fa3791e4abb4ce24d9420c748187 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Fri, 9 Feb 2024 19:38:58 -0500 Subject: [PATCH 023/119] Revert "reworked the layout to better support all screen sizes." This reverts commit e6e0055fa517e37d644a13c5a6b0953542750090. --- .../layout/view_expandable_notice_card.xml | 82 +++++++++++-------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/WooCommerce/src/main/res/layout/view_expandable_notice_card.xml b/WooCommerce/src/main/res/layout/view_expandable_notice_card.xml index 99d4fc7e176..6a318177d64 100644 --- a/WooCommerce/src/main/res/layout/view_expandable_notice_card.xml +++ b/WooCommerce/src/main/res/layout/view_expandable_notice_card.xml @@ -24,47 +24,58 @@ tools:textOff="@string/product_wip_title" tools:textOn="@string/product_wip_title_m5" /> - + tools:visibility="visible"> - - + - - + + + + + + + + From 1f237fed60c7367deb8af74b24c25c45ea956097 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 12 Feb 2024 10:59:47 +0100 Subject: [PATCH 024/119] Refactor state of the summary screen The goal is to avoid having multiple definitions of the selected method ID, which could lead to errors if they are not synced correctly. --- .../BlazeCampaignPaymentSummaryScreen.kt | 28 ++++++----- .../BlazeCampaignPaymentSummaryViewModel.kt | 50 +++++++++---------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt index e163a6d6bca..fb0ee04c129 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt @@ -84,7 +84,8 @@ fun BlazeCampaignPaymentSummaryScreen( ) PaymentMethod( - paymentMethodState = state.paymentMethodState, + paymentMethodsState = state.paymentMethodsState, + selectedPaymentMethod = state.selectedPaymentMethod, modifier = Modifier.fillMaxWidth() ) @@ -94,8 +95,7 @@ fun BlazeCampaignPaymentSummaryScreen( WCColoredButton( onClick = { /*TODO*/ }, text = stringResource(id = R.string.blaze_campaign_payment_summary_submit_campaign), - enabled = (state.paymentMethodState as? BlazeCampaignPaymentSummaryViewModel.PaymentMethodState.Success) - ?.isPaymentMethodSelected == true, + enabled = state.isPaymentMethodSelected, modifier = Modifier .fillMaxWidth() .padding(horizontal = dimensionResource(id = R.dimen.major_100)) @@ -182,14 +182,15 @@ private fun PaymentTotals( @Composable private fun PaymentMethod( - paymentMethodState: BlazeCampaignPaymentSummaryViewModel.PaymentMethodState, + paymentMethodsState: BlazeCampaignPaymentSummaryViewModel.PaymentMethodsState, + selectedPaymentMethod: BlazeRepository.PaymentMethod?, modifier: Modifier ) { Column(modifier) { Divider() - when (paymentMethodState) { - BlazeCampaignPaymentSummaryViewModel.PaymentMethodState.Loading -> { + when (paymentMethodsState) { + BlazeCampaignPaymentSummaryViewModel.PaymentMethodsState.Loading -> { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(dimensionResource(id = R.dimen.major_100)) @@ -204,21 +205,21 @@ private fun PaymentMethod( } } - is BlazeCampaignPaymentSummaryViewModel.PaymentMethodState.Success -> { + is BlazeCampaignPaymentSummaryViewModel.PaymentMethodsState.Success -> { PaymentMethodInfo( - paymentMethod = paymentMethodState.selectedPaymentMethod, - onClick = paymentMethodState.onClick, + paymentMethod = selectedPaymentMethod, + onClick = paymentMethodsState.onClick, modifier = Modifier ) } - is BlazeCampaignPaymentSummaryViewModel.PaymentMethodState.Error -> { + is BlazeCampaignPaymentSummaryViewModel.PaymentMethodsState.Error -> { Text( text = stringResource(id = R.string.blaze_campaign_payment_summary_error_loading_payment_methods), style = MaterialTheme.typography.body2, modifier = Modifier .padding(dimensionResource(id = R.dimen.major_100)) - .clickable(onClick = paymentMethodState.onRetry) + .clickable(onClick = paymentMethodsState.onRetry) ) } } @@ -283,7 +284,7 @@ fun BlazeCampaignPaymentSummaryScreenPreview() { startDate = Date(), currencyCode = "$" ), - paymentMethodState = BlazeCampaignPaymentSummaryViewModel.PaymentMethodState.Success( + paymentMethodsState = BlazeCampaignPaymentSummaryViewModel.PaymentMethodsState.Success( BlazeRepository.PaymentMethodsData( listOf( BlazeRepository.PaymentMethod( @@ -302,7 +303,8 @@ fun BlazeCampaignPaymentSummaryScreenPreview() { ) ), onClick = {} - ) + ), + selectedPaymentMethodId = "1" ), onBackClick = {} ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index d8429f1fe69..4a17fe69161 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -27,20 +27,16 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( clazz = String::class.java, key = "selectedPaymentMethodId" ) - private val paymentMethodState = MutableStateFlow(PaymentMethodState.Loading) + private val paymentMethodsState = MutableStateFlow(PaymentMethodsState.Loading) val viewState = combine( selectedPaymentMethodId, - paymentMethodState + paymentMethodsState ) { selectedPaymentMethodId, paymentMethodState -> ViewState( budget = navArgs.budget, - paymentMethodState = paymentMethodState.let { - when (it) { - is PaymentMethodState.Success -> it.copy(selectedPaymentMethodId = selectedPaymentMethodId) - else -> it - } - } + paymentMethodsState = paymentMethodState, + selectedPaymentMethodId = selectedPaymentMethodId ) }.asLiveData() @@ -55,8 +51,8 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( fun onPaymentMethodSelected(paymentMethodId: String) { selectedPaymentMethodId.value = paymentMethodId - val paymentMethodState = paymentMethodState.value - if (paymentMethodState is PaymentMethodState.Success && + val paymentMethodState = paymentMethodsState.value + if (paymentMethodState is PaymentMethodsState.Success && !paymentMethodState.paymentMethodsData.savedPaymentMethods.any { it.id == paymentMethodId } ) { fetchPaymentMethodData() @@ -64,7 +60,7 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( } private fun fetchPaymentMethodData() { - paymentMethodState.value = PaymentMethodState.Loading + paymentMethodsState.value = PaymentMethodsState.Loading launch { blazeRepository.fetchPaymentMethods().fold( onSuccess = { paymentMethodsData -> @@ -72,7 +68,7 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( selectedPaymentMethodId.value = paymentMethodsData.savedPaymentMethods.firstOrNull()?.id } - paymentMethodState.value = PaymentMethodState.Success( + paymentMethodsState.value = PaymentMethodsState.Success( paymentMethodsData = paymentMethodsData, onClick = { triggerEvent( @@ -85,7 +81,7 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( ) }, onFailure = { - paymentMethodState.value = PaymentMethodState.Error { fetchPaymentMethodData() } + paymentMethodsState.value = PaymentMethodsState.Error { fetchPaymentMethodData() } } ) } @@ -93,23 +89,27 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( data class ViewState( val budget: BlazeRepository.Budget, - val paymentMethodState: PaymentMethodState - ) + val paymentMethodsState: PaymentMethodsState, + private val selectedPaymentMethodId: String? + ) { + private val paymentMethodsData + get() = (paymentMethodsState as? PaymentMethodsState.Success)?.paymentMethodsData + val selectedPaymentMethod + get() = selectedPaymentMethodId?.let { id -> + paymentMethodsData?.savedPaymentMethods?.find { it.id == id } + } ?: paymentMethodsData?.savedPaymentMethods?.firstOrNull() + val isPaymentMethodSelected + get() = selectedPaymentMethod != null + } - sealed interface PaymentMethodState { - data object Loading : PaymentMethodState + sealed interface PaymentMethodsState { + data object Loading : PaymentMethodsState data class Success( val paymentMethodsData: PaymentMethodsData, - private val selectedPaymentMethodId: String? = null, val onClick: () -> Unit - ) : PaymentMethodState { - val selectedPaymentMethod = selectedPaymentMethodId?.let { id -> - paymentMethodsData.savedPaymentMethods.find { it.id == id } - } ?: paymentMethodsData.savedPaymentMethods.firstOrNull() - val isPaymentMethodSelected = selectedPaymentMethod != null - } + ) : PaymentMethodsState - data class Error(val onRetry: () -> Unit) : PaymentMethodState + data class Error(val onRetry: () -> Unit) : PaymentMethodsState } data class NavigateToPaymentsListScreen( From 4b487dd883498291bd0a83e0f894842d0a5bdae9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Feb 2024 11:12:22 +0100 Subject: [PATCH 025/119] Guidline to split the view --- .../android/ui/products/ProductListFragment.kt | 18 +++++++++--------- .../main/res/layout/fragment_product_list.xml | 14 +++++++++----- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index 49866a9841a..2ac53a94fb5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -115,6 +115,15 @@ class ProductListFragment : feedbackPrefs.getFeatureFeedbackSettings(FeatureFeedbackSettings.Feature.PRODUCT_VARIATIONS)?.feedbackState ?: FeatureFeedbackSettings.FeedbackState.UNANSWERED + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val transitionDuration = resources.getInteger(R.integer.default_fragment_transition).toLong() + val fadeThroughTransition = MaterialFadeThrough().apply { duration = transitionDuration } + enterTransition = fadeThroughTransition + exitTransition = fadeThroughTransition + reenterTransition = fadeThroughTransition + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) postponeEnterTransition() @@ -238,15 +247,6 @@ class ProductListFragment : super.onViewStateRestored(savedInstanceState) } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val transitionDuration = resources.getInteger(R.integer.default_fragment_transition).toLong() - val fadeThroughTransition = MaterialFadeThrough().apply { duration = transitionDuration } - enterTransition = fadeThroughTransition - exitTransition = fadeThroughTransition - reenterTransition = fadeThroughTransition - } - override fun onCreateMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_product_list_fragment, menu) diff --git a/WooCommerce/src/main/res/layout/fragment_product_list.xml b/WooCommerce/src/main/res/layout/fragment_product_list.xml index 209134277ee..2f71a8c3981 100644 --- a/WooCommerce/src/main/res/layout/fragment_product_list.xml +++ b/WooCommerce/src/main/res/layout/fragment_product_list.xml @@ -12,7 +12,7 @@ android:layout_width="0dp" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/detail_nav_container" + app:layout_constraintEnd_toStartOf="@+id/two_pane_layout_guideline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> @@ -120,18 +120,22 @@ + + From 3641eb211aa0b4ea8fce1804f80c74e048744314 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Feb 2024 11:21:36 +0100 Subject: [PATCH 026/119] Adjust ui for screen sizes --- .../ui/products/ProductListFragment.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index 2ac53a94fb5..249a62308f6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -58,6 +58,7 @@ import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.widgets.SkeletonView import com.woocommerce.android.widgets.WCEmptyView.EmptyViewType import dagger.hilt.android.AndroidEntryPoint +import org.wordpress.android.util.DisplayUtils import javax.inject.Inject @Suppress("LargeClass") @@ -74,6 +75,9 @@ class ProductListFragment : companion object { val TAG: String = ProductListFragment::class.java.simpleName const val PRODUCT_FILTER_RESULT_KEY = "product_filter_result" + + private const val TABLET_PANES_WIDTH_RATIO = 0.5F + private const val XL_TABLET_PANES_WIDTH_RATIO = 0.68F } @Inject @@ -122,6 +126,10 @@ class ProductListFragment : enterTransition = fadeThroughTransition exitTransition = fadeThroughTransition reenterTransition = fadeThroughTransition + + val navController = + childFragmentManager.findFragmentById(R.id.detail_nav_container)?.findNavController() + navController?.setGraph(R.navigation.nav_graph_products) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -161,6 +169,7 @@ class ProductListFragment : initAddProductFab(binding.addProductButton) addSelectionTracker() + binding.adjustUIForScreenSize() when { productListViewModel.isSearching() -> { @@ -174,6 +183,16 @@ class ProductListFragment : } } + private fun FragmentProductListBinding.adjustUIForScreenSize() { + if (DisplayUtils.isTablet(context)) { + twoPaneLayoutGuideline.setGuidelinePercent(TABLET_PANES_WIDTH_RATIO) + } else if (DisplayUtils.isXLargeTablet(context)) { + twoPaneLayoutGuideline.setGuidelinePercent(XL_TABLET_PANES_WIDTH_RATIO) + } else { + twoPaneLayoutGuideline.setGuidelinePercent(1.0f) + } + } + private fun addSelectionTracker() { tracker = SelectionTracker.Builder( "productSelection", // a string to identity our selection in the context of this fragment From bb579b2dfa2841f095c1e3ab7a031eb7f52a5825 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 12 Feb 2024 11:54:57 +0100 Subject: [PATCH 027/119] Update empty view of the payment method list screen --- .../payment/BlazeCampaignPaymentMethodsListScreen.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt index c8c45c75b12..2f08fbfb4f5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt @@ -1,11 +1,13 @@ package com.woocommerce.android.ui.blaze.creation.payment import android.content.res.Configuration +import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -27,6 +29,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.woocommerce.android.R @@ -220,12 +223,19 @@ private fun EmptyPaymentMethodsView( vertical = dimensionResource(id = R.dimen.major_200) ) ) { + Spacer(modifier = Modifier.weight(1f)) Text(text = stringResource(id = R.string.blaze_campaign_payment_list_empty_state_text)) - Spacer(modifier = Modifier.padding(dimensionResource(id = R.dimen.major_100))) + Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.major_200))) + Image( + painter = painterResource(id = R.drawable.img_empty_orders_all_fulfilled), + contentDescription = null + ) + Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.major_200))) WCColoredButton( onClick = onAddPaymentMethodClicked, text = stringResource(id = R.string.blaze_campaign_payment_list_add_payment_method_button) ) + Spacer(modifier = Modifier.weight(2f)) } } From 9367860d42bc1048f142de10b48a7aa5fdf89430 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Feb 2024 13:07:10 +0100 Subject: [PATCH 028/119] Extracted tablet related logic to a seprate class --- .../ui/products/ProductListFragment.kt | 43 +++++------ .../woocommerce/android/util/TabletHelper.kt | 72 +++++++++++++++++++ 2 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index 249a62308f6..ef82654818c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView.OnQueryTextListener +import androidx.constraintlayout.widget.Guideline import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.core.view.ViewGroupCompat @@ -54,11 +55,11 @@ import com.woocommerce.android.ui.products.ProductListViewModel.ProductListEvent import com.woocommerce.android.ui.products.ProductSortAndFiltersCard.ProductSortAndFilterListener import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.StringUtils +import com.woocommerce.android.util.TabletHelper import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.widgets.SkeletonView import com.woocommerce.android.widgets.WCEmptyView.EmptyViewType import dagger.hilt.android.AndroidEntryPoint -import org.wordpress.android.util.DisplayUtils import javax.inject.Inject @Suppress("LargeClass") @@ -71,13 +72,11 @@ class ProductListFragment : OnActionExpandListener, WCProductSearchTabView.ProductSearchTypeChangedListener, ActionMode.Callback, - MenuProvider { + MenuProvider, + TabletHelper.Screen { companion object { val TAG: String = ProductListFragment::class.java.simpleName const val PRODUCT_FILTER_RESULT_KEY = "product_filter_result" - - private const val TABLET_PANES_WIDTH_RATIO = 0.5F - private const val XL_TABLET_PANES_WIDTH_RATIO = 0.68F } @Inject @@ -92,6 +91,9 @@ class ProductListFragment : @Inject lateinit var addProductNavigator: AddProductNavigator + @Inject + lateinit var tabletHelper: TabletHelper + private var _productAdapter: ProductListAdapter? = null private val productAdapter: ProductListAdapter get() = _productAdapter!! @@ -119,21 +121,33 @@ class ProductListFragment : feedbackPrefs.getFeatureFeedbackSettings(FeatureFeedbackSettings.Feature.PRODUCT_VARIATIONS)?.feedbackState ?: FeatureFeedbackSettings.FeedbackState.UNANSWERED + override val twoPaneLayoutGuideline: Guideline + get() = binding.twoPaneLayoutGuideline + + override val lifecycleKeeper: Lifecycle + get() = viewLifecycleOwner.lifecycle + + override val navigation: TabletHelper.Screen.Navigation + get() = TabletHelper.Screen.Navigation( + childFragmentManager.findFragmentById(R.id.detail_nav_container)?.findNavController(), + R.navigation.nav_graph_products, + null, + ) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val transitionDuration = resources.getInteger(R.integer.default_fragment_transition).toLong() val fadeThroughTransition = MaterialFadeThrough().apply { duration = transitionDuration } enterTransition = fadeThroughTransition exitTransition = fadeThroughTransition reenterTransition = fadeThroughTransition - - val navController = - childFragmentManager.findFragmentById(R.id.detail_nav_container)?.findNavController() - navController?.setGraph(R.navigation.nav_graph_products) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + tabletHelper.onViewCreated(this) + postponeEnterTransition() requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) @@ -169,7 +183,6 @@ class ProductListFragment : initAddProductFab(binding.addProductButton) addSelectionTracker() - binding.adjustUIForScreenSize() when { productListViewModel.isSearching() -> { @@ -183,16 +196,6 @@ class ProductListFragment : } } - private fun FragmentProductListBinding.adjustUIForScreenSize() { - if (DisplayUtils.isTablet(context)) { - twoPaneLayoutGuideline.setGuidelinePercent(TABLET_PANES_WIDTH_RATIO) - } else if (DisplayUtils.isXLargeTablet(context)) { - twoPaneLayoutGuideline.setGuidelinePercent(XL_TABLET_PANES_WIDTH_RATIO) - } else { - twoPaneLayoutGuideline.setGuidelinePercent(1.0f) - } - } - private fun addSelectionTracker() { tracker = SelectionTracker.Builder( "productSelection", // a string to identity our selection in the context of this fragment diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt new file mode 100644 index 00000000000..7bad2ec634a --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt @@ -0,0 +1,72 @@ +package com.woocommerce.android.util + +import android.content.Context +import android.os.Bundle +import androidx.constraintlayout.widget.Guideline +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.navigation.NavController +import org.wordpress.android.util.DisplayUtils +import javax.inject.Inject + +class TabletHelper @Inject constructor( + private val context: Context, +) : DefaultLifecycleObserver { + private var screen: Screen? = null + + fun onViewCreated(screen: Screen) { + this.screen = screen + screen.lifecycleKeeper.addObserver(this) + } + + override fun onCreate(owner: LifecycleOwner) { + if (DisplayUtils.isTablet(context) || DisplayUtils.isXLargeTablet(context)) { + initNavFragment(screen!!.navigation) + } + } + + override fun onStart(owner: LifecycleOwner) { + adjustUIForScreenSize(screen!!.twoPaneLayoutGuideline) + } + + override fun onDestroy(owner: LifecycleOwner) { + screen!!.lifecycleKeeper.removeObserver(this) + screen = null + } + + private fun initNavFragment(navigation: Screen.Navigation) { + val navController = navigation.navController!! + val navGraphId = navigation.navGraph + val bundle = navigation.bundle + + navController.setGraph(navGraphId, bundle) + } + + private fun adjustUIForScreenSize(twoPaneLayoutGuideline: Guideline) { + if (DisplayUtils.isTablet(context)) { + twoPaneLayoutGuideline.setGuidelinePercent(TABLET_PANES_WIDTH_RATIO) + } else if (DisplayUtils.isXLargeTablet(context)) { + twoPaneLayoutGuideline.setGuidelinePercent(XL_TABLET_PANES_WIDTH_RATIO) + } else { + twoPaneLayoutGuideline.setGuidelinePercent(1.0f) + } + } + + private companion object { + const val TABLET_PANES_WIDTH_RATIO = 0.5F + const val XL_TABLET_PANES_WIDTH_RATIO = 0.68F + } + + interface Screen { + val twoPaneLayoutGuideline: Guideline + val navigation: Navigation + val lifecycleKeeper: Lifecycle + + data class Navigation( + val navController: NavController?, + val navGraph: Int, + val bundle: Bundle? + ) + } +} From d3b26b155e9d3ee9a4195ce3c1e74dc3b337ae58 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 12 Feb 2024 13:02:28 +0100 Subject: [PATCH 029/119] Creates UI for campaign created success bottom sheet --- .../BlazeCampaignSuccessBottomSheet.kt | 59 +++++++++++++++++++ ...BlazeCampaignSuccessBottomSheetFragment.kt | 20 +++++++ .../blaze_campaign_created_success.xml | 30 ++++++++++ .../blaze_campaign_created_success.xml | 30 ++++++++++ WooCommerce/src/main/res/values/strings.xml | 14 ++++- 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt create mode 100644 WooCommerce/src/main/res/drawable-night/blaze_campaign_created_success.xml create mode 100644 WooCommerce/src/main/res/drawable/blaze_campaign_created_success.xml diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt new file mode 100644 index 00000000000..58b28e6f65b --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt @@ -0,0 +1,59 @@ +package com.woocommerce.android.ui.blaze.creation.success + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.woocommerce.android.R +import com.woocommerce.android.ui.compose.component.WCColoredButton +import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews + +@Composable +fun BlazeCampaignSuccessBottomSheet(onDoneTapped: () -> Unit) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Image( + painter = painterResource(id = R.drawable.blaze_campaign_created_success), + contentDescription = "" + ) + Text( + text = stringResource(id = R.string.blaze_campaign_created_success_title), + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onSurface + ) + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(id = R.string.blaze_campaign_created_success_description), + style = MaterialTheme.typography.body1, + textAlign = TextAlign.Center, + color = MaterialTheme.colors.onSurface + ) + WCColoredButton( + onClick = onDoneTapped, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = R.string.blaze_campaign_created_success_done_button)) + } + } +} + +@LightDarkThemePreviews +@Composable +private fun BlazeCampaignSuccessBottomSheetPreview() { + BlazeCampaignSuccessBottomSheet(onDoneTapped = {}) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt new file mode 100644 index 00000000000..79923ea5d6e --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt @@ -0,0 +1,20 @@ +package com.woocommerce.android.ui.blaze.creation.success + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.woocommerce.android.ui.compose.composeView +import com.woocommerce.android.widgets.WCBottomSheetDialogFragment + +class BlazeCampaignSuccessBottomSheetFragment : WCBottomSheetDialogFragment() { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return composeView { + BlazeCampaignSuccessBottomSheet(::onDoneClicked) + } + } + + private fun onDoneClicked() { + dismiss() + } +} diff --git a/WooCommerce/src/main/res/drawable-night/blaze_campaign_created_success.xml b/WooCommerce/src/main/res/drawable-night/blaze_campaign_created_success.xml new file mode 100644 index 00000000000..c6c6c7bdc74 --- /dev/null +++ b/WooCommerce/src/main/res/drawable-night/blaze_campaign_created_success.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/WooCommerce/src/main/res/drawable/blaze_campaign_created_success.xml b/WooCommerce/src/main/res/drawable/blaze_campaign_created_success.xml new file mode 100644 index 00000000000..0d1fd724cd7 --- /dev/null +++ b/WooCommerce/src/main/res/drawable/blaze_campaign_created_success.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 307b3d61189..85320cd707a 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3807,6 +3807,7 @@ --> Blaze campaigns There was an error refreshing the list of campaigns. Please try again later. + @@ -3822,7 +3823,7 @@ Blaze campaign creation celebration --> All set! - The ad has been submistted for approval. We’ll send you a confirmation email once it’s approvied and running. + The ad has been submitted for approval. We’ll send you a confirmation email once it’s approvied and running. Got it @@ -3873,6 +3875,7 @@ Description %d characters remaining Suggested by AI + @@ -3896,6 +3899,7 @@ Promote Start typing country, state or city to see available options No location found.\nPlease try again. + @@ -3904,6 +3908,14 @@ URL parameters The product URL The site home + + + Ready to Go! + We\'re reviewing your campaign. It\'ll be live within 24 hours. Exciting times ahead for your sales! + Done + From 0d575a84942235408e4b14cc52b0ce1c82f696d8 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Feb 2024 14:11:22 +0100 Subject: [PATCH 030/119] List full screen by default --- WooCommerce/src/main/res/layout/fragment_product_list.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/res/layout/fragment_product_list.xml b/WooCommerce/src/main/res/layout/fragment_product_list.xml index 2f71a8c3981..4b7db2a5d13 100644 --- a/WooCommerce/src/main/res/layout/fragment_product_list.xml +++ b/WooCommerce/src/main/res/layout/fragment_product_list.xml @@ -125,6 +125,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + app:layout_constraintGuide_percent="1.0" tools:layout_constraintGuide_percent=".33" /> Date: Mon, 12 Feb 2024 14:30:45 +0100 Subject: [PATCH 031/119] Dynamically set nav host fragment --- .../ui/products/ProductListFragment.kt | 4 ++-- .../woocommerce/android/util/TabletHelper.kt | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index ef82654818c..5c8a64244c5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -127,9 +127,9 @@ class ProductListFragment : override val lifecycleKeeper: Lifecycle get() = viewLifecycleOwner.lifecycle - override val navigation: TabletHelper.Screen.Navigation + override val secondPaneNavigation: TabletHelper.Screen.Navigation get() = TabletHelper.Screen.Navigation( - childFragmentManager.findFragmentById(R.id.detail_nav_container)?.findNavController(), + childFragmentManager, R.navigation.nav_graph_products, null, ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt index 7bad2ec634a..ee11072c608 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt @@ -3,10 +3,12 @@ package com.woocommerce.android.util import android.content.Context import android.os.Bundle import androidx.constraintlayout.widget.Guideline +import androidx.fragment.app.FragmentManager import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner -import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import com.woocommerce.android.R import org.wordpress.android.util.DisplayUtils import javax.inject.Inject @@ -22,7 +24,7 @@ class TabletHelper @Inject constructor( override fun onCreate(owner: LifecycleOwner) { if (DisplayUtils.isTablet(context) || DisplayUtils.isXLargeTablet(context)) { - initNavFragment(screen!!.navigation) + initNavFragment(screen!!.secondPaneNavigation) } } @@ -36,11 +38,15 @@ class TabletHelper @Inject constructor( } private fun initNavFragment(navigation: Screen.Navigation) { - val navController = navigation.navController!! - val navGraphId = navigation.navGraph + val fragmentManager = navigation.fragmentManager + val navGraphId = navigation.navGraphId val bundle = navigation.bundle - navController.setGraph(navGraphId, bundle) + val navHostFragment = NavHostFragment.create(navGraphId, bundle) + + fragmentManager.beginTransaction() + .replace(R.id.detail_nav_container, navHostFragment) + .commit() } private fun adjustUIForScreenSize(twoPaneLayoutGuideline: Guideline) { @@ -55,17 +61,17 @@ class TabletHelper @Inject constructor( private companion object { const val TABLET_PANES_WIDTH_RATIO = 0.5F - const val XL_TABLET_PANES_WIDTH_RATIO = 0.68F + const val XL_TABLET_PANES_WIDTH_RATIO = 0.33F } interface Screen { val twoPaneLayoutGuideline: Guideline - val navigation: Navigation + val secondPaneNavigation: Navigation val lifecycleKeeper: Lifecycle data class Navigation( - val navController: NavController?, - val navGraph: Int, + val fragmentManager: FragmentManager, + val navGraphId: Int, val bundle: Bundle? ) } From 7bd3af7b2945700344f7e2ab5f3fb35fcd639651 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Feb 2024 14:43:04 +0100 Subject: [PATCH 032/119] Check feature flag --- .../kotlin/com/woocommerce/android/util/TabletHelper.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt index ee11072c608..b14433d9ef7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt @@ -18,20 +18,21 @@ class TabletHelper @Inject constructor( private var screen: Screen? = null fun onViewCreated(screen: Screen) { + if (!FeatureFlag.BETTER_TABLETS_SUPPORT_PRODUCTS.isEnabled()) return + this.screen = screen screen.lifecycleKeeper.addObserver(this) } override fun onCreate(owner: LifecycleOwner) { + if (!FeatureFlag.BETTER_TABLETS_SUPPORT_PRODUCTS.isEnabled()) return + if (DisplayUtils.isTablet(context) || DisplayUtils.isXLargeTablet(context)) { initNavFragment(screen!!.secondPaneNavigation) + adjustUIForScreenSize(screen!!.twoPaneLayoutGuideline) } } - override fun onStart(owner: LifecycleOwner) { - adjustUIForScreenSize(screen!!.twoPaneLayoutGuideline) - } - override fun onDestroy(owner: LifecycleOwner) { screen!!.lifecycleKeeper.removeObserver(this) screen = null From 6ba6d6cfec50fde21248e08e590822439f6c769c Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Feb 2024 14:44:34 +0100 Subject: [PATCH 033/119] When instead of if else --- .../com/woocommerce/android/util/TabletHelper.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt index b14433d9ef7..11c40de14ef 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt @@ -51,12 +51,14 @@ class TabletHelper @Inject constructor( } private fun adjustUIForScreenSize(twoPaneLayoutGuideline: Guideline) { - if (DisplayUtils.isTablet(context)) { - twoPaneLayoutGuideline.setGuidelinePercent(TABLET_PANES_WIDTH_RATIO) - } else if (DisplayUtils.isXLargeTablet(context)) { - twoPaneLayoutGuideline.setGuidelinePercent(XL_TABLET_PANES_WIDTH_RATIO) - } else { - twoPaneLayoutGuideline.setGuidelinePercent(1.0f) + when { + DisplayUtils.isTablet(context) -> { + twoPaneLayoutGuideline.setGuidelinePercent(TABLET_PANES_WIDTH_RATIO) + } + DisplayUtils.isXLargeTablet(context) -> { + twoPaneLayoutGuideline.setGuidelinePercent(XL_TABLET_PANES_WIDTH_RATIO) + } + else -> twoPaneLayoutGuideline.setGuidelinePercent(1.0f) } } From 6dae97cba103216eb0fd108507a1705b70d04b0a Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Feb 2024 14:48:02 +0100 Subject: [PATCH 034/119] Lazily init needed for tablets params --- .../android/ui/products/ProductListFragment.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index 5c8a64244c5..94c2acae2ac 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -11,7 +11,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView.OnQueryTextListener -import androidx.constraintlayout.widget.Guideline import androidx.core.view.MenuCompat import androidx.core.view.MenuProvider import androidx.core.view.ViewGroupCompat @@ -121,18 +120,17 @@ class ProductListFragment : feedbackPrefs.getFeatureFeedbackSettings(FeatureFeedbackSettings.Feature.PRODUCT_VARIATIONS)?.feedbackState ?: FeatureFeedbackSettings.FeedbackState.UNANSWERED - override val twoPaneLayoutGuideline: Guideline - get() = binding.twoPaneLayoutGuideline + override val twoPaneLayoutGuideline by lazy { binding.twoPaneLayoutGuideline } - override val lifecycleKeeper: Lifecycle - get() = viewLifecycleOwner.lifecycle + override val lifecycleKeeper: Lifecycle by lazy { viewLifecycleOwner.lifecycle } - override val secondPaneNavigation: TabletHelper.Screen.Navigation - get() = TabletHelper.Screen.Navigation( + override val secondPaneNavigation by lazy { + TabletHelper.Screen.Navigation( childFragmentManager, R.navigation.nav_graph_products, null, ) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) From 8ad2dd291f83481a1d0b1e2f7ebb7201664774ff Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 12 Feb 2024 12:52:19 +0100 Subject: [PATCH 035/119] Update `navigation-fragment-ktx` to 2.7.7 --- .../android/ui/blaze/BlazeCampaignCreationScreen.kt | 3 ++- .../login/jetpack/main/JetpackActivationMainScreen.kt | 3 ++- .../depositsummary/PaymentsHubDepositSummaryView.kt | 10 +++++----- .../android/ui/shipping/InstallWCShippingScreen.kt | 5 +++-- settings.gradle | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt index f2e4f646fa9..0ad773959ad 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.animation.with import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -84,7 +85,7 @@ fun BlazeCampaignCreationScreen( ) { paddingValues -> AnimatedContent( targetState = viewState, - transitionSpec = { fadeIn() with fadeOut() } + transitionSpec = { fadeIn() togetherWith fadeOut() } ) { targetState -> when (targetState) { is BlazeCreationViewState.Intro -> BlazeCreationIntroScreen( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt index daa3415069c..55747f59c8b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith import androidx.compose.animation.with import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image @@ -109,7 +110,7 @@ fun JetpackActivationMainScreen( transition.AnimatedContent( contentKey = { it is JetpackActivationMainViewModel.ViewState.ErrorViewState }, transitionSpec = { - fadeIn(animationSpec = tween(DefaultDurationMillis, delayMillis = DefaultDurationMillis)) with + fadeIn(animationSpec = tween(DefaultDurationMillis, delayMillis = DefaultDurationMillis)) togetherWith fadeOut(animationSpec = tween(DefaultDurationMillis)) }, modifier = Modifier.weight(1f) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt index ce8465b8c54..64f7033f21b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt @@ -17,6 +17,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith import androidx.compose.animation.with import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -138,7 +139,7 @@ fun PaymentsHubDepositSummaryView( .fillMaxWidth() .background(colorResource(id = R.color.color_surface)) ) { - val pagerState = rememberPagerState(initialPage = selectedPage) + val pagerState = rememberPagerState(initialPage = selectedPage) { pageCount } val isInitialLoad = remember { mutableStateOf(true) } val currencies = overview.infoPerCurrency.keys.toList() @@ -164,7 +165,6 @@ fun PaymentsHubDepositSummaryView( } HorizontalPager( - pageCount = pageCount, state = pagerState ) { pageIndex -> Column( @@ -558,11 +558,11 @@ private fun FundsNumber( targetState = valueToDisplay to valueAmount, transitionSpec = { if (animationPlayed) { - EnterTransition.None with ExitTransition.None + EnterTransition.None togetherWith ExitTransition.None } else if (targetState.second > initialState.second) { - slideInVertically { -it } with slideOutVertically { it } + slideInVertically { -it } togetherWith slideOutVertically { it } } else { - slideInVertically { it } with slideOutVertically { -it } + slideInVertically { it } togetherWith slideOutVertically { -it } } }, label = "AnimatedFundsNumber" diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt index 472c1008cc2..5d0582dd724 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith import androidx.compose.animation.with import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -49,10 +50,10 @@ fun InstallWCShippingScreen(viewState: ViewState) { // Apply a fade-in/fade-out globally, // then each child will animate the individual components separately fadeIn(tween(500, delayMillis = 500)) - .with(fadeOut(tween(500, easing = LinearOutSlowInEasing))) + .togetherWith(fadeOut(tween(500, easing = LinearOutSlowInEasing))) } else { // No-op animation, each screen will define animations for specific components separately - EnterTransition.None.with(ExitTransition.None) + EnterTransition.None.togetherWith(ExitTransition.None) } } ) { targetState -> diff --git a/settings.gradle b/settings.gradle index b8da08eaa6b..cf0dbbdfb44 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ pluginManagement { gradle.ext.detektVersion = '1.19.0' gradle.ext.kotlinVersion = '1.9.22' gradle.ext.measureBuildsVersion = '2.0.3' - gradle.ext.navigationVersion = '2.6.0' + gradle.ext.navigationVersion = '2.7.7' gradle.ext.sentryVersion = '3.5.0' gradle.ext.violationCommentsVersion = '1.69.0' From 1e9874731659590bc6140ec306ca435900001cd8 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 12 Feb 2024 13:18:13 +0100 Subject: [PATCH 036/119] Remove unused imports --- .../android/ui/blaze/BlazeCampaignCreationScreen.kt | 3 +-- .../hub/depositsummary/PaymentsHubDepositSummaryView.kt | 7 +++---- .../android/ui/shipping/InstallWCShippingScreen.kt | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt index 0ad773959ad..429ddcd1d96 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith -import androidx.compose.animation.with import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -85,7 +84,7 @@ fun BlazeCampaignCreationScreen( ) { paddingValues -> AnimatedContent( targetState = viewState, - transitionSpec = { fadeIn() togetherWith fadeOut() } + transitionSpec = { fadeIn() togetherWith fadeOut() } ) { targetState -> when (targetState) { is BlazeCreationViewState.Intro -> BlazeCreationIntroScreen( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt index 64f7033f21b..b98a4934d9d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt @@ -18,7 +18,6 @@ import androidx.compose.animation.shrinkVertically import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith -import androidx.compose.animation.with import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -558,11 +557,11 @@ private fun FundsNumber( targetState = valueToDisplay to valueAmount, transitionSpec = { if (animationPlayed) { - EnterTransition.None togetherWith ExitTransition.None + EnterTransition.None togetherWith ExitTransition.None } else if (targetState.second > initialState.second) { - slideInVertically { -it } togetherWith slideOutVertically { it } + slideInVertically { -it } togetherWith slideOutVertically { it } } else { - slideInVertically { it } togetherWith slideOutVertically { -it } + slideInVertically { it } togetherWith slideOutVertically { -it } } }, label = "AnimatedFundsNumber" diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt index 5d0582dd724..5a74ea52c59 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.animation.core.updateTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith -import androidx.compose.animation.with import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.material.MaterialTheme From 2a337af33eabfd5be6f495f2dce4d0cdcabf9d09 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 12 Feb 2024 13:21:07 +0100 Subject: [PATCH 037/119] Update RELEASE-NOTES.txt --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 47c1cf66738..92a598b0388 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -6,7 +6,7 @@ 17.3 ----- - +- [**] Fixed navigation issues [https://github.com/woocommerce/woocommerce-android/pull/10775] 17.3 ----- From 83e7e7045b15a513f1df4dc90f96325706918850 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 12 Feb 2024 14:58:06 +0100 Subject: [PATCH 038/119] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 92a598b0388..00b41bf5580 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -13,6 +13,7 @@ - [*] [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] - [*] [Internal] Enhanced product variation delete confirmation dialog visibility and functionality across device rotations [https://github.com/woocommerce/woocommerce-android/pull/10664] - [*] [Internal] Added a text along with the receipts file when it is shared [https://github.com/woocommerce/woocommerce-android/pull/10681] +- [**] Fixed navigation issues [https://github.com/woocommerce/woocommerce-android/pull/10775] 17.2 ----- From 47652784ae7c7e753d3f25d435e63832497776fb Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 12 Feb 2024 15:09:55 +0100 Subject: [PATCH 039/119] Better naming for the tablet helper --- .../android/ui/products/ProductListFragment.kt | 10 +++++----- .../{TabletHelper.kt => TabletLayoutSetupHelper.kt} | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/util/{TabletHelper.kt => TabletLayoutSetupHelper.kt} (98%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index 94c2acae2ac..db29da3702d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -54,7 +54,7 @@ import com.woocommerce.android.ui.products.ProductListViewModel.ProductListEvent import com.woocommerce.android.ui.products.ProductSortAndFiltersCard.ProductSortAndFilterListener import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.StringUtils -import com.woocommerce.android.util.TabletHelper +import com.woocommerce.android.util.TabletLayoutSetupHelper import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import com.woocommerce.android.widgets.SkeletonView import com.woocommerce.android.widgets.WCEmptyView.EmptyViewType @@ -72,7 +72,7 @@ class ProductListFragment : WCProductSearchTabView.ProductSearchTypeChangedListener, ActionMode.Callback, MenuProvider, - TabletHelper.Screen { + TabletLayoutSetupHelper.Screen { companion object { val TAG: String = ProductListFragment::class.java.simpleName const val PRODUCT_FILTER_RESULT_KEY = "product_filter_result" @@ -91,7 +91,7 @@ class ProductListFragment : lateinit var addProductNavigator: AddProductNavigator @Inject - lateinit var tabletHelper: TabletHelper + lateinit var tabletLayoutSetupHelper: TabletLayoutSetupHelper private var _productAdapter: ProductListAdapter? = null private val productAdapter: ProductListAdapter @@ -125,7 +125,7 @@ class ProductListFragment : override val lifecycleKeeper: Lifecycle by lazy { viewLifecycleOwner.lifecycle } override val secondPaneNavigation by lazy { - TabletHelper.Screen.Navigation( + TabletLayoutSetupHelper.Screen.Navigation( childFragmentManager, R.navigation.nav_graph_products, null, @@ -144,7 +144,7 @@ class ProductListFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - tabletHelper.onViewCreated(this) + tabletLayoutSetupHelper.onViewCreated(this) postponeEnterTransition() requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt similarity index 98% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt index 11c40de14ef..00afb3aae04 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt @@ -12,7 +12,7 @@ import com.woocommerce.android.R import org.wordpress.android.util.DisplayUtils import javax.inject.Inject -class TabletHelper @Inject constructor( +class TabletLayoutSetupHelper @Inject constructor( private val context: Context, ) : DefaultLifecycleObserver { private var screen: Screen? = null From c36b8dc697e1ea77da44ba9aede64f3267bf0459 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 12 Feb 2024 15:12:55 +0100 Subject: [PATCH 040/119] Fix release notes --- RELEASE-NOTES.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 00b41bf5580..7dec127b401 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,17 +3,13 @@ 17.4 ----- - -17.3 ------ -- [**] Fixed navigation issues [https://github.com/woocommerce/woocommerce-android/pull/10775] - 17.3 ----- - [*] [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] - [*] [Internal] Enhanced product variation delete confirmation dialog visibility and functionality across device rotations [https://github.com/woocommerce/woocommerce-android/pull/10664] - [*] [Internal] Added a text along with the receipts file when it is shared [https://github.com/woocommerce/woocommerce-android/pull/10681] - [**] Fixed navigation issues [https://github.com/woocommerce/woocommerce-android/pull/10775] +- [**] Fixed navigation issues [https://github.com/woocommerce/woocommerce-android/pull/10775] 17.2 ----- From ab0378f24f52fc64a7e0087b0112b7cc3e65673f Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 12 Feb 2024 15:13:24 +0100 Subject: [PATCH 041/119] Fix release notes --- RELEASE-NOTES.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 7dec127b401..4cbaa246d68 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -9,7 +9,6 @@ - [*] [Internal] Enhanced product variation delete confirmation dialog visibility and functionality across device rotations [https://github.com/woocommerce/woocommerce-android/pull/10664] - [*] [Internal] Added a text along with the receipts file when it is shared [https://github.com/woocommerce/woocommerce-android/pull/10681] - [**] Fixed navigation issues [https://github.com/woocommerce/woocommerce-android/pull/10775] -- [**] Fixed navigation issues [https://github.com/woocommerce/woocommerce-android/pull/10775] 17.2 ----- From 5523a309ecae1226e7a187d1c7ce9d5385577f4c Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 12 Feb 2024 15:38:59 +0100 Subject: [PATCH 042/119] Format code --- .../main/JetpackActivationMainScreen.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt index 55747f59c8b..ca7039abbd9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt @@ -110,16 +110,19 @@ fun JetpackActivationMainScreen( transition.AnimatedContent( contentKey = { it is JetpackActivationMainViewModel.ViewState.ErrorViewState }, transitionSpec = { - fadeIn(animationSpec = tween(DefaultDurationMillis, delayMillis = DefaultDurationMillis)) togetherWith - fadeOut(animationSpec = tween(DefaultDurationMillis)) + fadeIn( + animationSpec = tween( + DefaultDurationMillis, delayMillis = DefaultDurationMillis + ) + ) togetherWith fadeOut(animationSpec = tween(DefaultDurationMillis)) }, modifier = Modifier.weight(1f) ) { targetState -> when (targetState) { is JetpackActivationMainViewModel.ViewState.ProgressViewState -> ProgressState( - viewState = targetState, - onContinueClick = onContinueClick + viewState = targetState, onContinueClick = onContinueClick ) + is JetpackActivationMainViewModel.ViewState.ErrorViewState -> ErrorState( viewState = targetState, onGetHelpClick = onGetHelpClick, @@ -285,12 +288,9 @@ private fun AnimatedVisibilityScope.ErrorState( Spacer(modifier = Modifier.weight(1f)) val buttonsModifier = Modifier .fillMaxWidth() - .animateEnterExit( - enter = slideInVertically(animationSpec = tween(delayMillis = DefaultDurationMillis)) { fullHeight -> - fullHeight - }, - exit = slideOutVertically { fullHeight -> fullHeight } - ) + .animateEnterExit(enter = slideInVertically(animationSpec = tween(delayMillis = DefaultDurationMillis)) { fullHeight -> + fullHeight + }, exit = slideOutVertically { fullHeight -> fullHeight }) if (viewState.errorCode != FORBIDDEN_ERROR_CODE) { val retryButton = when (viewState.stepType) { JetpackActivationMainViewModel.StepType.Installation -> From 9194bd458c61be78a69122a911927e399045d41d Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 12 Feb 2024 16:05:25 +0100 Subject: [PATCH 043/119] Show correct account's username and email on the payment list --- .../BlazeCampaignPaymentMethodsListScreen.kt | 36 +++++++++++-------- ...lazeCampaignPaymentMethodsListViewModel.kt | 8 ++++- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt index 2f08fbfb4f5..286325a0ba5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt @@ -68,10 +68,7 @@ private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymen when (viewState) { is BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList -> { PaymentMethodsList( - paymentMethods = viewState.paymentMethods, - selectedPaymentMethod = viewState.selectedPaymentMethod, - onPaymentMethodClicked = viewState.onPaymentMethodClicked, - onAddPaymentMethodClicked = viewState.onAddPaymentMethodClicked, + state = viewState, modifier = Modifier.padding(paddingValues) ) } @@ -89,27 +86,26 @@ private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymen @Composable private fun PaymentMethodsList( - paymentMethods: List, - selectedPaymentMethod: PaymentMethod?, - onPaymentMethodClicked: (PaymentMethod) -> Unit, - onAddPaymentMethodClicked: () -> Unit, + state: BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList, modifier: Modifier ) { Column(modifier) { PaymentMethodsHeader(Modifier.fillMaxWidth()) - if (paymentMethods.isEmpty()) { + if (state.paymentMethods.isEmpty()) { EmptyPaymentMethodsView( - onAddPaymentMethodClicked = onAddPaymentMethodClicked, + onAddPaymentMethodClicked = state.onAddPaymentMethodClicked, modifier = modifier .fillMaxWidth() .weight(1f) ) } else { PaymentMethodsListView( - paymentMethods = paymentMethods, - selectedPaymentMethod = selectedPaymentMethod!!, - onPaymentMethodClicked = onPaymentMethodClicked, - onAddPaymentMethodClicked = onAddPaymentMethodClicked, + paymentMethods = state.paymentMethods, + accountEmail = state.accountEmail, + accountUsername = state.accountUsername, + selectedPaymentMethod = state.selectedPaymentMethod!!, + onPaymentMethodClicked = state.onPaymentMethodClicked, + onAddPaymentMethodClicked = state.onAddPaymentMethodClicked, modifier = modifier .fillMaxWidth() .weight(1f) @@ -135,6 +131,8 @@ private fun PaymentMethodsHeader(modifier: Modifier = Modifier) { private fun PaymentMethodsListView( paymentMethods: List, selectedPaymentMethod: PaymentMethod, + accountEmail: String, + accountUsername: String, onPaymentMethodClicked: (PaymentMethod) -> Unit, onAddPaymentMethodClicked: () -> Unit, modifier: Modifier @@ -153,7 +151,11 @@ private fun PaymentMethodsListView( item { Text( - text = stringResource(id = R.string.blaze_campaign_payment_list_hint, "username", "email"), + text = stringResource( + id = R.string.blaze_campaign_payment_list_hint, + accountUsername, + accountEmail + ), style = MaterialTheme.typography.caption, color = MaterialTheme.colors.onSurface.copy( alpha = ContentAlpha.medium @@ -258,6 +260,8 @@ private fun EmptyPaymentMethodsListPreview() { viewState = BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList( paymentMethods = emptyList(), selectedPaymentMethod = null, + accountEmail = "email", + accountUsername = "username", onPaymentMethodClicked = {}, onAddPaymentMethodClicked = {}, onDismiss = {} @@ -295,6 +299,8 @@ private fun PaymentMethodsListPreview() { viewState = BlazeCampaignPaymentMethodsListViewModel.ViewState.PaymentMethodsList( paymentMethods = paymentMethods, selectedPaymentMethod = paymentMethods.first(), + accountEmail = "email", + accountUsername = "username", onPaymentMethodClicked = {}, onAddPaymentMethodClicked = {}, onDismiss = {} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index 84877ab6db1..a55097023ed 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import com.woocommerce.android.ui.blaze.BlazeRepository +import com.woocommerce.android.ui.login.AccountRepository import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs @@ -12,7 +13,8 @@ import javax.inject.Inject @HiltViewModel class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( - savedStateHandle: SavedStateHandle + savedStateHandle: SavedStateHandle, + private val accountRepository: AccountRepository ) : ScopedViewModel(savedStateHandle) { private val navArgs by savedStateHandle.navArgs() @@ -31,6 +33,8 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( selectedPaymentMethod = navArgs.paymentMethodsData.savedPaymentMethods.firstOrNull { it.id == navArgs.selectedPaymentMethodId }, + accountEmail = accountRepository.getUserAccount()?.email ?: "", + accountUsername = accountRepository.getUserAccount()?.userName ?: "", onPaymentMethodClicked = { triggerEvent(MultiLiveEvent.Event.ExitWithResult(it.id)) }, @@ -52,6 +56,8 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( data class PaymentMethodsList( val paymentMethods: List, val selectedPaymentMethod: BlazeRepository.PaymentMethod?, + val accountEmail: String, + val accountUsername: String, val onPaymentMethodClicked: (BlazeRepository.PaymentMethod) -> Unit, val onAddPaymentMethodClicked: () -> Unit, override val onDismiss: () -> Unit From 4316f2ddd21f078552133b6cf72dc83e41686d60 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 12 Feb 2024 12:58:17 +0100 Subject: [PATCH 044/119] Handle displaying WebView for adding new payment method --- .../BlazeCampaignPaymentMethodsListScreen.kt | 25 ++++++++++++++----- ...lazeCampaignPaymentMethodsListViewModel.kt | 15 ++++++++--- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt index 286325a0ba5..bc4341e0f0c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt @@ -35,11 +35,13 @@ import androidx.compose.ui.tooling.preview.Preview import com.woocommerce.android.R import com.woocommerce.android.model.CreditCardType import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethod -import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethodUrls +import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewAuthenticator import com.woocommerce.android.ui.compose.component.Toolbar import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.component.WCTextButton +import com.woocommerce.android.ui.compose.component.WCWebView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import org.wordpress.android.fluxc.network.UserAgent @Composable fun BlazeCampaignPaymentMethodsListScreen(viewModel: BlazeCampaignPaymentMethodsListViewModel) { @@ -51,7 +53,9 @@ fun BlazeCampaignPaymentMethodsListScreen(viewModel: BlazeCampaignPaymentMethods } @Composable -private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymentMethodsListViewModel.ViewState) { +private fun BlazeCampaignPaymentMethodsListScreen( + viewState: BlazeCampaignPaymentMethodsListViewModel.ViewState +) { Scaffold( topBar = { Toolbar( @@ -75,8 +79,10 @@ private fun BlazeCampaignPaymentMethodsListScreen(viewState: BlazeCampaignPaymen is BlazeCampaignPaymentMethodsListViewModel.ViewState.AddPaymentMethodWebView -> { AddPaymentMethodWebView( - urls = viewState.urls, + formUrl = viewState.formUrl, onUrlLoaded = viewState.onUrlLoaded, + userAgent = viewState.userAgent, + wpComWebViewAuthenticator = viewState.wpComWebViewAuthenticator, modifier = Modifier.padding(paddingValues) ) } @@ -241,14 +247,21 @@ private fun EmptyPaymentMethodsView( } } -@Suppress("UNUSED_PARAMETER") @Composable fun AddPaymentMethodWebView( - urls: PaymentMethodUrls, + formUrl: String, onUrlLoaded: (String) -> Unit, + userAgent: UserAgent, + wpComWebViewAuthenticator: WPComWebViewAuthenticator, modifier: Modifier ) { - TODO("Not yet implemented") + WCWebView( + url = formUrl, + userAgent = userAgent, + wpComAuthenticator = wpComWebViewAuthenticator, + onUrlLoaded = onUrlLoaded, + modifier = modifier + ) } @Preview(name = "dark", uiMode = Configuration.UI_MODE_NIGHT_YES) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index a55097023ed..70683896e7e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -3,20 +3,23 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import com.woocommerce.android.ui.blaze.BlazeRepository +import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewAuthenticator import com.woocommerce.android.ui.login.AccountRepository import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow +import org.wordpress.android.fluxc.network.UserAgent import javax.inject.Inject @HiltViewModel class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val accountRepository: AccountRepository + private val accountRepository: AccountRepository, + val userAgent: UserAgent, + val wpComWebViewAuthenticator: WPComWebViewAuthenticator ) : ScopedViewModel(savedStateHandle) { - private val navArgs by savedStateHandle.navArgs() private val _viewState = MutableStateFlow( @@ -45,7 +48,9 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( ) private fun addPaymentMethodWebView(): ViewState = ViewState.AddPaymentMethodWebView( - urls = navArgs.paymentMethodsData.addPaymentMethodUrls, + formUrl = navArgs.paymentMethodsData.addPaymentMethodUrls.formUrl, + userAgent = userAgent, + wpComWebViewAuthenticator = wpComWebViewAuthenticator, onUrlLoaded = { /* TODO */ }, onDismiss = { _viewState.value = paymentMethodsListState() } ) @@ -64,7 +69,9 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( ) : ViewState data class AddPaymentMethodWebView( - val urls: BlazeRepository.PaymentMethodUrls, + val formUrl: String, + val userAgent: UserAgent, + val wpComWebViewAuthenticator: WPComWebViewAuthenticator, val onUrlLoaded: (String) -> Unit, override val onDismiss: () -> Unit ) : ViewState From a2ade2acb00757a35e037b5895e362bac6b8d328 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 12 Feb 2024 12:59:02 +0100 Subject: [PATCH 045/119] Add logic of extracting payment method ID from the WebView callback --- ...lazeCampaignPaymentMethodsListViewModel.kt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index 70683896e7e..f91f8b795ec 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -5,12 +5,14 @@ import androidx.lifecycle.asLiveData import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewAuthenticator import com.woocommerce.android.ui.login.AccountRepository +import com.woocommerce.android.util.WooLog import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ScopedViewModel import com.woocommerce.android.viewmodel.navArgs import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import org.wordpress.android.fluxc.network.UserAgent +import java.net.URL import javax.inject.Inject @HiltViewModel @@ -51,7 +53,24 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( formUrl = navArgs.paymentMethodsData.addPaymentMethodUrls.formUrl, userAgent = userAgent, wpComWebViewAuthenticator = wpComWebViewAuthenticator, - onUrlLoaded = { /* TODO */ }, + onUrlLoaded = { url -> + if (url.startsWith(navArgs.paymentMethodsData.addPaymentMethodUrls.successUrl)) { + runCatching { + URL(url).query?.split("&") + ?.firstOrNull { it.startsWith("payment_method_id=") } + ?.substringAfter("=") + .let { requireNotNull(it) } + }.fold( + onSuccess = { + MultiLiveEvent.Event.ExitWithResult(it) + }, + onFailure = { + WooLog.e(WooLog.T.BLAZE, "Failed to extract payment method id from URL: $url", it) + _viewState.value = paymentMethodsListState() + } + ) + } + }, onDismiss = { _viewState.value = paymentMethodsListState() } ) From c00a62c8ae7a926a753882d7f25c232fa8600560 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 12 Feb 2024 13:20:08 +0100 Subject: [PATCH 046/119] Dismiss WebView on back button --- .../BlazeCampaignPaymentMethodsListScreen.kt | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt index bc4341e0f0c..cdb082722df 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListScreen.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.blaze.creation.payment import android.content.res.Configuration +import androidx.activity.compose.BackHandler import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -35,13 +36,11 @@ import androidx.compose.ui.tooling.preview.Preview import com.woocommerce.android.R import com.woocommerce.android.model.CreditCardType import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethod -import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewAuthenticator import com.woocommerce.android.ui.compose.component.Toolbar import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.component.WCTextButton import com.woocommerce.android.ui.compose.component.WCWebView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground -import org.wordpress.android.fluxc.network.UserAgent @Composable fun BlazeCampaignPaymentMethodsListScreen(viewModel: BlazeCampaignPaymentMethodsListViewModel) { @@ -79,10 +78,7 @@ private fun BlazeCampaignPaymentMethodsListScreen( is BlazeCampaignPaymentMethodsListViewModel.ViewState.AddPaymentMethodWebView -> { AddPaymentMethodWebView( - formUrl = viewState.formUrl, - onUrlLoaded = viewState.onUrlLoaded, - userAgent = viewState.userAgent, - wpComWebViewAuthenticator = viewState.wpComWebViewAuthenticator, + state = viewState, modifier = Modifier.padding(paddingValues) ) } @@ -249,17 +245,18 @@ private fun EmptyPaymentMethodsView( @Composable fun AddPaymentMethodWebView( - formUrl: String, - onUrlLoaded: (String) -> Unit, - userAgent: UserAgent, - wpComWebViewAuthenticator: WPComWebViewAuthenticator, + state: BlazeCampaignPaymentMethodsListViewModel.ViewState.AddPaymentMethodWebView, modifier: Modifier ) { + BackHandler { + state.onDismiss() + } + WCWebView( - url = formUrl, - userAgent = userAgent, - wpComAuthenticator = wpComWebViewAuthenticator, - onUrlLoaded = onUrlLoaded, + url = state.formUrl, + userAgent = state.userAgent, + wpComAuthenticator = state.wpComWebViewAuthenticator, + onUrlLoaded = state.onUrlLoaded, modifier = modifier ) } From b0306c33bce8ff1db3bddd93e617314084872603 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 12 Feb 2024 13:40:51 +0100 Subject: [PATCH 047/119] Show a Snackbar when payment method is added --- .../BlazeCampaignPaymentMethodsListFragment.kt | 5 +++++ .../BlazeCampaignPaymentMethodsListViewModel.kt | 14 ++++++++++---- WooCommerce/src/main/res/values/strings.xml | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt index 3925094c17b..507db79db8d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListFragment.kt @@ -8,11 +8,13 @@ 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.base.UIMessageResolver import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.viewmodel.MultiLiveEvent import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class BlazeCampaignPaymentMethodsListFragment : BaseFragment() { @@ -24,6 +26,8 @@ class BlazeCampaignPaymentMethodsListFragment : BaseFragment() { get() = AppBarStatus.Hidden private val viewModel: BlazeCampaignPaymentMethodsListViewModel by viewModels() + @Inject + lateinit var uiMessageResolver: UIMessageResolver override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return composeView { @@ -45,6 +49,7 @@ class BlazeCampaignPaymentMethodsListFragment : BaseFragment() { key = SELECTED_PAYMENT_METHOD_KEY, result = event.data ) + is MultiLiveEvent.Event.ShowSnackbar -> uiMessageResolver.showSnack(event.message) } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index f91f8b795ec..1e5b65894c0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData +import com.woocommerce.android.R import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewAuthenticator import com.woocommerce.android.ui.login.AccountRepository @@ -19,8 +20,8 @@ import javax.inject.Inject class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val accountRepository: AccountRepository, - val userAgent: UserAgent, - val wpComWebViewAuthenticator: WPComWebViewAuthenticator + private val userAgent: UserAgent, + private val wpComWebViewAuthenticator: WPComWebViewAuthenticator ) : ScopedViewModel(savedStateHandle) { private val navArgs by savedStateHandle.navArgs() @@ -61,8 +62,13 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( ?.substringAfter("=") .let { requireNotNull(it) } }.fold( - onSuccess = { - MultiLiveEvent.Event.ExitWithResult(it) + onSuccess = { paymentMethodId -> + triggerEvent( + MultiLiveEvent.Event.ShowSnackbar( + R.string.blaze_campaign_payment_added_successfully + ) + ) + MultiLiveEvent.Event.ExitWithResult(paymentMethodId) }, onFailure = { WooLog.e(WooLog.T.BLAZE, "Failed to extract payment method id from URL: $url", it) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 8f12f75492c..1b74e6c716d 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3890,6 +3890,7 @@ All transactions are secure and encrypted Credits cards are retrieved from the following WordPress.com account: %1$s <%2$s> Add new card + Credit card added successfully From cb8e82b50fe185d607490444931d9c48962c6e82 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 12 Feb 2024 16:26:09 +0100 Subject: [PATCH 048/119] Format code --- .../ui/login/jetpack/main/JetpackActivationMainScreen.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt index ca7039abbd9..37b2f506ed9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt @@ -288,9 +288,12 @@ private fun AnimatedVisibilityScope.ErrorState( Spacer(modifier = Modifier.weight(1f)) val buttonsModifier = Modifier .fillMaxWidth() - .animateEnterExit(enter = slideInVertically(animationSpec = tween(delayMillis = DefaultDurationMillis)) { fullHeight -> - fullHeight - }, exit = slideOutVertically { fullHeight -> fullHeight }) + .animateEnterExit( + enter = slideInVertically(animationSpec = tween(delayMillis = DefaultDurationMillis)) { fullHeight -> + fullHeight + }, + exit = slideOutVertically { fullHeight -> fullHeight } + ) if (viewState.errorCode != FORBIDDEN_ERROR_CODE) { val retryButton = when (viewState.stepType) { JetpackActivationMainViewModel.StepType.Installation -> From 638dfdc4a2c1b18962de1dab44eaab756c375baf Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 12 Feb 2024 16:34:38 +0100 Subject: [PATCH 049/119] Navigate back to flow starting screen on campaign creation success --- .../intro/BlazeCampaignCreationIntroFragment.kt | 8 ++++++-- .../payment/BlazeCampaignPaymentSummaryFragment.kt | 7 +++++++ .../payment/BlazeCampaignPaymentSummaryScreen.kt | 11 +++++++---- .../payment/BlazeCampaignPaymentSummaryViewModel.kt | 13 +++++++++++++ .../nav_graph_blaze_campaign_creation.xml | 4 ++++ .../src/main/res/navigation/nav_graph_main.xml | 9 ++++++--- 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroFragment.kt index fa668cfc2d2..f9d9eb11b38 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import androidx.navigation.navOptions import com.woocommerce.android.R import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateSafely @@ -41,10 +42,13 @@ class BlazeCampaignCreationIntroFragment : BaseFragment() { when (event) { is BlazeCampaignCreationIntroViewModel.ShowCampaignCreationForm -> { findNavController().navigateSafely( - BlazeCampaignCreationIntroFragmentDirections + directions = BlazeCampaignCreationIntroFragmentDirections .actionBlazeCampaignCreationIntroFragmentToBlazeCampaignCreationPreviewFragment( productId = event.productId - ) + ), + navOptions = navOptions { + popUpTo(R.id.blazeCampaignCreationIntroFragment) { inclusive = true } + } ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt index 0ff640f3e82..074f3306129 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt @@ -6,7 +6,9 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import com.woocommerce.android.R import com.woocommerce.android.ui.base.BaseFragment +import com.woocommerce.android.ui.blaze.creation.payment.BlazeCampaignPaymentSummaryViewModel.NavigateToStartingScreen import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus @@ -36,7 +38,12 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { MultiLiveEvent.Event.Exit -> findNavController().navigateUp() + is NavigateToStartingScreen -> navigateBackToStartingScreen() } } } + + private fun navigateBackToStartingScreen() { + findNavController().popBackStack(R.id.blazeCampaignCreationPreviewFragment, inclusive = true) + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt index e6e21fda6ea..5ca1a0c5e32 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt @@ -47,7 +47,8 @@ fun BlazeCampaignPaymentSummaryScreen(viewModel: BlazeCampaignPaymentSummaryView viewModel.viewState.observeAsState().value?.let { BlazeCampaignPaymentSummaryScreen( state = it, - onBackClick = viewModel::onBackClicked + onBackClick = viewModel::onBackClicked, + onSubmitCampaign = viewModel::onSubmitCampaign ) } } @@ -55,7 +56,8 @@ fun BlazeCampaignPaymentSummaryScreen(viewModel: BlazeCampaignPaymentSummaryView @Composable fun BlazeCampaignPaymentSummaryScreen( state: BlazeCampaignPaymentSummaryViewModel.ViewState, - onBackClick: () -> Unit + onBackClick: () -> Unit, + onSubmitCampaign: () -> Unit ) { val context = LocalContext.current @@ -92,7 +94,7 @@ fun BlazeCampaignPaymentSummaryScreen( Divider() WCColoredButton( - onClick = { /*TODO*/ }, + onClick = onSubmitCampaign, text = stringResource(id = R.string.blaze_campaign_payment_summary_submit_campaign), modifier = Modifier .fillMaxWidth() @@ -302,7 +304,8 @@ fun BlazeCampaignPaymentSummaryScreenPreview() { onClick = {} ) ), - onBackClick = {} + onBackClick = {}, + onSubmitCampaign = {} ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index ab02eb172c8..1a8f235279f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -36,6 +36,10 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( triggerEvent(MultiLiveEvent.Event.Exit) } + fun campaignCreatedSuccessfully() { + triggerEvent(NavigateToStartingScreen(campaignCreatedSuccessfully = true)) + } + private fun fetchPaymentMethodData() { paymentMethodState.value = PaymentMethodState.Loading launch { @@ -51,6 +55,11 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( } } + fun onSubmitCampaign() { + // TODO show loading and trigger campaign creation + triggerEvent(NavigateToStartingScreen(campaignCreatedSuccessfully = true)) + } + data class ViewState( val budget: BlazeRepository.Budget, val paymentMethodState: PaymentMethodState @@ -67,4 +76,8 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( data class Error(val onRetry: () -> Unit) : PaymentMethodState } + + data class NavigateToStartingScreen( + val campaignCreatedSuccessfully: Boolean + ) : MultiLiveEvent.Event() } 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 b5fc72c39f6..fe7a217d00b 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 @@ -136,4 +136,8 @@ android:name="budget" app:argType="com.woocommerce.android.ui.blaze.BlazeRepository$Budget" /> + diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index f9daa8b705a..1c7b3c81b97 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -87,6 +87,9 @@ + + android:label="scan_to_update_inventory" /> + app:argType="string[]" + app:nullable="true" /> Date: Mon, 12 Feb 2024 17:00:44 +0100 Subject: [PATCH 050/119] Prep for handling click on product --- .../android/util/TabletLayoutSetupHelper.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt index 00afb3aae04..457ad6a8cd8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt @@ -7,8 +7,10 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import androidx.navigation.NavDirections import androidx.navigation.fragment.NavHostFragment import com.woocommerce.android.R +import com.woocommerce.android.extensions.navigateSafely import org.wordpress.android.util.DisplayUtils import javax.inject.Inject @@ -17,6 +19,8 @@ class TabletLayoutSetupHelper @Inject constructor( ) : DefaultLifecycleObserver { private var screen: Screen? = null + private lateinit var navHostFragment: NavHostFragment + fun onViewCreated(screen: Screen) { if (!FeatureFlag.BETTER_TABLETS_SUPPORT_PRODUCTS.isEnabled()) return @@ -24,6 +28,20 @@ class TabletLayoutSetupHelper @Inject constructor( screen.lifecycleKeeper.addObserver(this) } + fun onItemClicked( + getActionToLaunch: () -> NavDirections, + navigateWithRootNavController: () -> Unit + ) { + if (!FeatureFlag.BETTER_TABLETS_SUPPORT_PRODUCTS.isEnabled() || + DisplayUtils.isTablet(context) || + DisplayUtils.isXLargeTablet(context) + ) { + navigateWithRootNavController() + } else { + navHostFragment.navController.navigateSafely(getActionToLaunch()) + } + } + override fun onCreate(owner: LifecycleOwner) { if (!FeatureFlag.BETTER_TABLETS_SUPPORT_PRODUCTS.isEnabled()) return @@ -43,7 +61,7 @@ class TabletLayoutSetupHelper @Inject constructor( val navGraphId = navigation.navGraphId val bundle = navigation.bundle - val navHostFragment = NavHostFragment.create(navGraphId, bundle) + navHostFragment = NavHostFragment.create(navGraphId, bundle) fragmentManager.beginTransaction() .replace(R.id.detail_nav_container, navHostFragment) @@ -55,9 +73,11 @@ class TabletLayoutSetupHelper @Inject constructor( DisplayUtils.isTablet(context) -> { twoPaneLayoutGuideline.setGuidelinePercent(TABLET_PANES_WIDTH_RATIO) } + DisplayUtils.isXLargeTablet(context) -> { twoPaneLayoutGuideline.setGuidelinePercent(XL_TABLET_PANES_WIDTH_RATIO) } + else -> twoPaneLayoutGuideline.setGuidelinePercent(1.0f) } } From 72afb2451c180e0b5c98f06544c2da06ed7278ff Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 12 Feb 2024 17:48:58 +0100 Subject: [PATCH 051/119] Ad new preference to save if Blaze campaign was created successfully --- .../main/kotlin/com/woocommerce/android/AppPrefs.kt | 11 +++++++++++ .../kotlin/com/woocommerce/android/AppPrefsWrapper.kt | 2 ++ 2 files changed, 13 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt index c6ad35944a0..d7bf58204f1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -128,6 +128,7 @@ object AppPrefs { CHA_CHING_SOUND_ISSUE_DIALOG_DISMISSED, TIMES_AI_PRODUCT_CREATION_SURVEY_DISPLAYED, AI_PRODUCT_CREATION_SURVEY_DISMISSED, + IS_COMING_FROM_BLAZE_CAMPAIGN_CREATIO_SUCCESS, } /** @@ -1082,6 +1083,16 @@ object AppPrefs { value = value ) + var isComingFromBlazeCampaignCreationSuccess: Boolean + get() = getBoolean( + key = DeletablePrefKey.IS_COMING_FROM_BLAZE_CAMPAIGN_CREATIO_SUCCESS, + default = false + ) + set(value) = setBoolean( + key = DeletablePrefKey.IS_COMING_FROM_BLAZE_CAMPAIGN_CREATIO_SUCCESS, + value = value + ) + fun incrementAIDescriptionTooltipShownNumber() { val currentTotal = getInt(DeletablePrefKey.NUMBER_OF_TIMES_AI_DESCRIPTION_TOOLTIP_SHOWN, 0) setInt(DeletablePrefKey.NUMBER_OF_TIMES_AI_DESCRIPTION_TOOLTIP_SHOWN, currentTotal + 1) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt index dc6cd251cf6..a487bd063ab 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt @@ -42,6 +42,8 @@ class AppPrefsWrapper @Inject constructor() { var isAiProductCreationSurveyDismissed by AppPrefs::isAiProductCreationSurveyDismissed + var isComingFromBlazeCampaignCreationSuccess by AppPrefs::isComingFromBlazeCampaignCreationSuccess + fun getAppInstallationDate() = AppPrefs.installationDate fun getReceiptUrl(localSiteId: Int, remoteSiteId: Long, selfHostedSiteId: Long, orderId: Long) = From c07aabffa51bfce219556f742837eab087661324 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 12 Feb 2024 17:49:11 +0100 Subject: [PATCH 052/119] Show campaign creation success bottom sheet on My Store tab --- .../android/ui/blaze/MyStoreBlazeViewModel.kt | 8 ++++++++ .../payment/BlazeCampaignPaymentSummaryViewModel.kt | 9 ++++++++- .../success/BlazeCampaignSuccessBottomSheetFragment.kt | 8 ++++++++ .../woocommerce/android/ui/mystore/MyStoreFragment.kt | 8 ++++++++ .../res/navigation/nav_graph_blaze_campaign_creation.xml | 4 ---- WooCommerce/src/main/res/navigation/nav_graph_main.xml | 6 +++++- 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/MyStoreBlazeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/MyStoreBlazeViewModel.kt index 9306979e65b..ff2aed45777 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/MyStoreBlazeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/MyStoreBlazeViewModel.kt @@ -185,6 +185,12 @@ class MyStoreBlazeViewModel @Inject constructor( ) } + fun checkBlazeCampaignCreationSuccess() { + if (prefsWrapper.isComingFromBlazeCampaignCreationSuccess) { + triggerEvent(ShowBlazeCampaignCreationSuccess) + } + } + sealed interface MyStoreBlazeCampaignState { object Hidden : MyStoreBlazeCampaignState data class NoCampaign( @@ -213,4 +219,6 @@ class MyStoreBlazeViewModel @Inject constructor( val url: String, val urlToTriggerExit: String ) : MultiLiveEvent.Event() + + object ShowBlazeCampaignCreationSuccess : MultiLiveEvent.Event() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index 1a8f235279f..3c47e5bf8d0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethodsData import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -15,7 +16,8 @@ import javax.inject.Inject @HiltViewModel class BlazeCampaignPaymentSummaryViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val blazeRepository: BlazeRepository + private val blazeRepository: BlazeRepository, + private val appPrefsWrapper: AppPrefsWrapper ) : ScopedViewModel(savedStateHandle) { private val navArgs = BlazeCampaignPaymentSummaryFragmentArgs.fromSavedStateHandle(savedStateHandle) @@ -57,6 +59,11 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( fun onSubmitCampaign() { // TODO show loading and trigger campaign creation + onCampaignCreationSuccess() + } + + private fun onCampaignCreationSuccess() { + appPrefsWrapper.isComingFromBlazeCampaignCreationSuccess = true triggerEvent(NavigateToStartingScreen(campaignCreatedSuccessfully = true)) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt index 79923ea5d6e..7a1872b6846 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt @@ -4,11 +4,19 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.widgets.WCBottomSheetDialogFragment +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +@AndroidEntryPoint class BlazeCampaignSuccessBottomSheetFragment : WCBottomSheetDialogFragment() { + @Inject + lateinit var appPrefsWrapper: AppPrefsWrapper + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + appPrefsWrapper.isComingFromBlazeCampaignCreationSuccess = false return composeView { BlazeCampaignSuccessBottomSheet(::onDoneClicked) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt index be3ea3d2742..a73be17f8dd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt @@ -48,6 +48,7 @@ import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.blaze.MyStoreBlazeView import com.woocommerce.android.ui.blaze.MyStoreBlazeViewModel import com.woocommerce.android.ui.blaze.MyStoreBlazeViewModel.MyStoreBlazeCampaignState +import com.woocommerce.android.ui.blaze.MyStoreBlazeViewModel.ShowBlazeCampaignCreationSuccess import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.feedback.SurveyType @@ -273,8 +274,15 @@ class MyStoreFragment : ) ) } + + is ShowBlazeCampaignCreationSuccess -> { + findNavController().navigateSafely( + MyStoreFragmentDirections.actionMyStoreToBlazeCampaignCreationSuccess() + ) + } } } + myStoreBlazeViewModel.checkBlazeCampaignCreationSuccess() } private fun openBlazeCreationFlow(productId: Long?) { 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 fe7a217d00b..b5fc72c39f6 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 @@ -136,8 +136,4 @@ android:name="budget" app:argType="com.woocommerce.android.ui.blaze.BlazeRepository$Budget" /> - diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index 1c7b3c81b97..5f7c9062b6e 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -89,7 +89,7 @@ app:destination="@id/blazeCampaignListFragment" /> + app:destination="@id/blazeCampaignSuccessBottomSheetFragmentOnMyStore" /> + From 81e392b6f7f89720f5a6a5c83dad333091bd08ad Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 12 Feb 2024 18:09:19 +0100 Subject: [PATCH 053/119] Improve bottomsheet UI --- .../BlazeCampaignSuccessBottomSheet.kt | 75 ++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt index 58b28e6f65b..3fa15bb8a3d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt @@ -1,53 +1,76 @@ package com.woocommerce.android.ui.blaze.creation.success import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +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.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.woocommerce.android.R +import com.woocommerce.android.R.dimen +import com.woocommerce.android.R.drawable +import com.woocommerce.android.R.string +import com.woocommerce.android.ui.compose.component.BottomSheetHandle import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews @Composable fun BlazeCampaignSuccessBottomSheet(onDoneTapped: () -> Unit) { Column( - modifier = Modifier + Modifier .fillMaxWidth() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) + .clip( + RoundedCornerShape( + topStart = dimensionResource(id = dimen.corner_radius_large), + topEnd = dimensionResource(id = dimen.corner_radius_large) + ) + ) ) { - Image( - painter = painterResource(id = R.drawable.blaze_campaign_created_success), - contentDescription = "" - ) - Text( - text = stringResource(id = R.string.blaze_campaign_created_success_title), - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onSurface - ) - Text( - modifier = Modifier.padding(horizontal = 16.dp), - text = stringResource(id = R.string.blaze_campaign_created_success_description), - style = MaterialTheme.typography.body1, - textAlign = TextAlign.Center, - color = MaterialTheme.colors.onSurface - ) - WCColoredButton( - onClick = onDoneTapped, - modifier = Modifier.fillMaxWidth() + Spacer(modifier = Modifier.height(8.dp)) + BottomSheetHandle(Modifier.align(Alignment.CenterHorizontally)) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .padding(top = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, ) { - Text(text = stringResource(id = R.string.blaze_campaign_created_success_done_button)) + Image( + painter = painterResource(id = drawable.blaze_campaign_created_success), + contentDescription = "" + ) + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(id = string.blaze_campaign_created_success_title), + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onSurface + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier.padding(horizontal = 20.dp), + text = stringResource(id = string.blaze_campaign_created_success_description), + style = MaterialTheme.typography.body1, + textAlign = TextAlign.Center, + color = MaterialTheme.colors.onSurface + ) + Spacer(modifier = Modifier.height(32.dp)) + WCColoredButton( + onClick = onDoneTapped, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = string.blaze_campaign_created_success_done_button)) + } } } } From 1d819d592bcc4bad9726fe9383d979b88ebec1e3 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 12 Feb 2024 18:36:34 +0100 Subject: [PATCH 054/119] Display success bottomsheet when flow triggered from product detail --- .../woocommerce/android/ui/mystore/MyStoreFragment.kt | 2 +- .../android/ui/products/ProductDetailFragment.kt | 9 ++++++++- .../android/ui/products/ProductDetailViewModel.kt | 8 ++++++++ WooCommerce/src/main/res/navigation/nav_graph_main.xml | 8 ++++---- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt index a73be17f8dd..4de95e93772 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt @@ -277,7 +277,7 @@ class MyStoreFragment : is ShowBlazeCampaignCreationSuccess -> { findNavController().navigateSafely( - MyStoreFragmentDirections.actionMyStoreToBlazeCampaignCreationSuccess() + NavGraphMainDirections.actionGlobalBlazeCampaignSuccessBottomSheetFragment() ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt index 638676f60ab..9f20de714b5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt @@ -170,6 +170,7 @@ class ProductDetailFragment : ) initializeViews(savedInstanceState) initializeViewModel() + viewModel.checkBlazeCampaignCreationSuccess() } override fun onDestroyView() { @@ -385,6 +386,12 @@ class ProductDetailFragment : ) is ShowAiProductCreationSurveyBottomSheet -> openAIProductCreationSurveyBottomSheet() + is ProductDetailViewModel.ShowBlazeCampaignCreationSuccess -> { + findNavController().navigateSafely( + NavGraphMainDirections.actionGlobalBlazeCampaignSuccessBottomSheetFragment() + ) + } + else -> event.isHandled = false } } @@ -396,7 +403,7 @@ class ProductDetailFragment : ) } - private fun showAIProductDescriptionBottomSheet(title: String, description: String?) { + fun showAIProductDescriptionBottomSheet(title: String, description: String?) { findNavController().navigateSafely( ProductDetailFragmentDirections.actionProductDetailFragmentToAIProductDescriptionBottomSheetFragment( title, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt index 23ae378e719..2fb7d2ddf75 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt @@ -2418,6 +2418,12 @@ class ProductDetailViewModel @Inject constructor( return storedProduct.value?.subscription?.length != viewState.productDraft?.subscription?.length } + fun checkBlazeCampaignCreationSuccess() { + if (appPrefsWrapper.isComingFromBlazeCampaignCreationSuccess) { + triggerEvent(ShowBlazeCampaignCreationSuccess) + } + } + /** * Sealed class that handles the back navigation for the product detail screens while providing a common * interface for managing them as a single type. Currently used in all the product sub detail screens when @@ -2467,6 +2473,8 @@ class ProductDetailViewModel @Inject constructor( object ShowAiProductCreationSurveyBottomSheet : Event() + object ShowBlazeCampaignCreationSuccess : Event() + /** * [productDraft] is used for the UI. Any updates to the fields in the UI would update this model. * [storedProduct.value] is the [Product] model that is fetched from the API and available in the local db. diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index 5f7c9062b6e..91066854a65 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -87,9 +87,6 @@ - + From fa46bb1c0b58072dfed465215ac13a5c52a54b18 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 12 Feb 2024 23:15:31 +0100 Subject: [PATCH 055/119] Display success bottomsheet when flow triggered from blaze campaign screen --- .../ui/blaze/campaigs/BlazeCampaignListFragment.kt | 9 +++++++++ .../ui/blaze/campaigs/BlazeCampaignListViewModel.kt | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt index 8a65f50080d..7a87265c7d6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.R import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource +import com.woocommerce.android.ui.blaze.campaigs.BlazeCampaignListViewModel.ShowBlazeCampaignCreationSuccess import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus @@ -59,12 +60,20 @@ class BlazeCampaignListFragment : BaseFragment() { } else { openBlazeWebView(event.url, event.source) } + is BlazeCampaignListViewModel.ShowCampaignDetails -> openCampaignDetails( event.url, event.urlToTriggerExit ) + + is ShowBlazeCampaignCreationSuccess -> { + findNavController().navigateSafely( + NavGraphMainDirections.actionGlobalBlazeCampaignSuccessBottomSheetFragment() + ) + } } } + viewModel.checkBlazeCampaignCreationSuccess() } private fun openBlazeCreationFlow() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListViewModel.kt index 518d86759f5..c9710dda35f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListViewModel.kt @@ -95,6 +95,12 @@ class BlazeCampaignListViewModel @Inject constructor( isCampaignCelebrationShown.value = false } + fun checkBlazeCampaignCreationSuccess() { + if (appPrefsWrapper.isComingFromBlazeCampaignCreationSuccess) { + triggerEvent(ShowBlazeCampaignCreationSuccess) + } + } + private suspend fun loadCampaignsFor(page: Int) { val result = blazeCampaignsStore.fetchBlazeCampaigns(selectedSite.get(), page) if (result.isError || result.model == null) { @@ -174,4 +180,6 @@ class BlazeCampaignListViewModel @Inject constructor( val url: String, val urlToTriggerExit: String ) : Event() + + object ShowBlazeCampaignCreationSuccess : Event() } From c425f7e11b2d52e0b542d2cf2a32f1330786221a Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 12 Feb 2024 23:48:09 +0100 Subject: [PATCH 056/119] Add rounded corners to bottom sheet --- .../BlazeCampaignSuccessBottomSheet.kt | 82 +++++++++---------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt index 3fa15bb8a3d..a6c6aea9249 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt @@ -8,11 +8,11 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -27,49 +27,47 @@ import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews @Composable fun BlazeCampaignSuccessBottomSheet(onDoneTapped: () -> Unit) { - Column( - Modifier - .fillMaxWidth() - .clip( - RoundedCornerShape( - topStart = dimensionResource(id = dimen.corner_radius_large), - topEnd = dimensionResource(id = dimen.corner_radius_large) - ) - ) + Surface( + shape = RoundedCornerShape( + topStart = dimensionResource(id = dimen.minor_100), + topEnd = dimensionResource(id = dimen.minor_100) + ) ) { - Spacer(modifier = Modifier.height(8.dp)) - BottomSheetHandle(Modifier.align(Alignment.CenterHorizontally)) - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - .padding(top = 24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image( - painter = painterResource(id = drawable.blaze_campaign_created_success), - contentDescription = "" - ) - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = stringResource(id = string.blaze_campaign_created_success_title), - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onSurface - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - modifier = Modifier.padding(horizontal = 20.dp), - text = stringResource(id = string.blaze_campaign_created_success_description), - style = MaterialTheme.typography.body1, - textAlign = TextAlign.Center, - color = MaterialTheme.colors.onSurface - ) - Spacer(modifier = Modifier.height(32.dp)) - WCColoredButton( - onClick = onDoneTapped, - modifier = Modifier.fillMaxWidth() + Column { + Spacer(modifier = Modifier.height(8.dp)) + BottomSheetHandle(Modifier.align(Alignment.CenterHorizontally)) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + .padding(top = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, ) { - Text(text = stringResource(id = string.blaze_campaign_created_success_done_button)) + Image( + painter = painterResource(id = drawable.blaze_campaign_created_success), + contentDescription = "" + ) + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(id = string.blaze_campaign_created_success_title), + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onSurface + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier.padding(horizontal = 20.dp), + text = stringResource(id = string.blaze_campaign_created_success_description), + style = MaterialTheme.typography.body1, + textAlign = TextAlign.Center, + color = MaterialTheme.colors.onSurface + ) + Spacer(modifier = Modifier.height(32.dp)) + WCColoredButton( + onClick = onDoneTapped, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(id = string.blaze_campaign_created_success_done_button)) + } } } } From b954802585eb7287b4d47ddd4aeda1e97d37a8e7 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 13 Feb 2024 09:32:00 +0100 Subject: [PATCH 057/119] Minor UI tweaks --- .../BlazeCampaignSuccessBottomSheet.kt | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt index a6c6aea9249..7c28e360b0d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheet.kt @@ -26,49 +26,51 @@ import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews @Composable -fun BlazeCampaignSuccessBottomSheet(onDoneTapped: () -> Unit) { +fun BlazeCampaignSuccessBottomSheet( + onDoneTapped: () -> Unit, + modifier: Modifier = Modifier +) { Surface( shape = RoundedCornerShape( topStart = dimensionResource(id = dimen.minor_100), topEnd = dimensionResource(id = dimen.minor_100) ) ) { - Column { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { Spacer(modifier = Modifier.height(8.dp)) BottomSheetHandle(Modifier.align(Alignment.CenterHorizontally)) - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - .padding(top = 24.dp), - horizontalAlignment = Alignment.CenterHorizontally, + Spacer(modifier = Modifier.height(30.dp)) + Image( + painter = painterResource(id = drawable.blaze_campaign_created_success), + contentDescription = "" + ) + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(id = string.blaze_campaign_created_success_title), + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onSurface + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier.padding(horizontal = 20.dp), + text = stringResource(id = string.blaze_campaign_created_success_description), + style = MaterialTheme.typography.body1, + textAlign = TextAlign.Center, + color = MaterialTheme.colors.onSurface + ) + Spacer(modifier = Modifier.height(32.dp)) + WCColoredButton( + onClick = onDoneTapped, + modifier = Modifier.fillMaxWidth() ) { - Image( - painter = painterResource(id = drawable.blaze_campaign_created_success), - contentDescription = "" - ) - Spacer(modifier = Modifier.height(24.dp)) - Text( - text = stringResource(id = string.blaze_campaign_created_success_title), - style = MaterialTheme.typography.h6, - color = MaterialTheme.colors.onSurface - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - modifier = Modifier.padding(horizontal = 20.dp), - text = stringResource(id = string.blaze_campaign_created_success_description), - style = MaterialTheme.typography.body1, - textAlign = TextAlign.Center, - color = MaterialTheme.colors.onSurface - ) - Spacer(modifier = Modifier.height(32.dp)) - WCColoredButton( - onClick = onDoneTapped, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(id = string.blaze_campaign_created_success_done_button)) - } + Text(text = stringResource(id = string.blaze_campaign_created_success_done_button)) } + Spacer(modifier = Modifier.height(16.dp)) } } } From 402304b15c62e44359b1118ede8c1cd7fe7b2f4f Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 13 Feb 2024 10:07:51 +0100 Subject: [PATCH 058/119] Use navigation from TabletLayoutSetupHelper --- .../android/ui/products/ProductListFragment.kt | 14 ++++++++++++-- .../android/util/TabletLayoutSetupHelper.kt | 13 ++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index db29da3702d..b4e42db5060 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -160,8 +160,18 @@ class ProductListFragment : loadMoreListener = this, currencyFormatter = currencyFormatter, clickListener = { id, sharedView -> - binding.addProductButton.hide() - onProductClick(id, sharedView) + tabletLayoutSetupHelper.onItemClicked( + getTabletActionToNavigateTo = { + NavGraphMainDirections.actionGlobalProductDetailFragment( + remoteProductId = id, + isTrashEnabled = true, + ) + }, + navigateWithPhoneNavigation = { + binding.addProductButton.hide() + onProductClick(id, sharedView) + } + ) } ) binding.productsRecycler.layoutManager = LinearLayoutManager(requireActivity()) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt index 457ad6a8cd8..98764b599e3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt @@ -29,16 +29,15 @@ class TabletLayoutSetupHelper @Inject constructor( } fun onItemClicked( - getActionToLaunch: () -> NavDirections, - navigateWithRootNavController: () -> Unit + getTabletActionToNavigateTo: () -> NavDirections, + navigateWithPhoneNavigation: () -> Unit ) { - if (!FeatureFlag.BETTER_TABLETS_SUPPORT_PRODUCTS.isEnabled() || - DisplayUtils.isTablet(context) || - DisplayUtils.isXLargeTablet(context) + if (FeatureFlag.BETTER_TABLETS_SUPPORT_PRODUCTS.isEnabled() && + (DisplayUtils.isTablet(context) || DisplayUtils.isXLargeTablet(context)) ) { - navigateWithRootNavController() + navHostFragment.navController.navigateSafely(getTabletActionToNavigateTo()) } else { - navHostFragment.navController.navigateSafely(getActionToLaunch()) + navigateWithPhoneNavigation() } } From 9f065c32323df31929afab97fcb8bb2e5611d21c Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 13 Feb 2024 10:34:23 +0100 Subject: [PATCH 059/119] Show skeleton earlier --- .../woocommerce/android/ui/products/ProductDetailViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt index 23ae378e719..398ab875815 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt @@ -1351,6 +1351,7 @@ class ProductDetailViewModel @Inject constructor( } launch { + viewState = viewState.copy(isSkeletonShown = true) // fetch product val productInDb = productRepository.getProductAsync(remoteProductId) if (productInDb != null) { @@ -1363,7 +1364,6 @@ class ProductDetailViewModel @Inject constructor( fetchProductPassword(remoteProductId) } } else { - viewState = viewState.copy(isSkeletonShown = true) fetchProduct(remoteProductId) } viewState = viewState.copy(isSkeletonShown = false) From 6432f3d208cd46fc05360fea7d811b88e949b0ba Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 13 Feb 2024 11:08:38 +0100 Subject: [PATCH 060/119] Revert navigationVersion bump --- WooCommerce/build.gradle | 3 +-- settings.gradle | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 3dffa9f78c5..1dbba01f9ab 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -280,8 +280,7 @@ dependencies { implementation "com.github.bumptech.glide:glide:$glideVersion" kapt "com.github.bumptech.glide:compiler:$glideVersion" implementation "com.github.bumptech.glide:volley-integration:$glideVersion@aar" - implementation 'com.google.android.play:app-update-ktx:2.1.0' - implementation 'com.google.android.play:review-ktx:2.0.1' + implementation "com.google.android.play:core:$googlePlayCoreVersion" implementation 'com.google.android.gms:play-services-code-scanner:16.1.0' diff --git a/settings.gradle b/settings.gradle index 2880cc753a9..039cbe375b5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ pluginManagement { gradle.ext.detektVersion = '1.19.0' gradle.ext.kotlinVersion = '1.8.21' gradle.ext.measureBuildsVersion = '2.0.3' - gradle.ext.navigationVersion = '2.6.0' + gradle.ext.navigationVersion = '2.5.3' gradle.ext.sentryVersion = '3.5.0' gradle.ext.violationCommentsVersion = '1.69.0' From f6c8f1138c986618f86f4c55bae4c37c9e873eb0 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Tue, 13 Feb 2024 11:20:37 +0100 Subject: [PATCH 061/119] Revert navigationVersion bump --- RELEASE-NOTES.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index c76a96a1d6a..858a31789b7 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -7,6 +7,11 @@ 17.3 ----- - [*] [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] + +17.2.1 +----- +- [**] Fixed navigation bug causing app to crash in some scenarios [https://github.com/woocommerce/woocommerce-android/pull/10786] + 17.2 ----- - [**] [Available for users with WooCommerce version of 8.7+, which is not released yet] Every order have a receipt now. The receipts can be shared via many apps installed on the phone [https://github.com/woocommerce/woocommerce-android/pull/10650] From 8bde2d2b589c7d3c5509ef33d757a9de9de68f7e Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 11:20:43 +0100 Subject: [PATCH 062/119] Update the remaining character count logic to match the web --- .../BlazeCampaignCreationAdDestinationParametersViewModel.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt index 3f2af0794f0..dc8d816eb08 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.blaze.creation.destination import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import com.woocommerce.android.util.getBaseUrl +import com.woocommerce.android.util.joinToString import com.woocommerce.android.util.joinToUrl import com.woocommerce.android.util.parseParameters import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult @@ -19,7 +20,7 @@ class BlazeCampaignCreationAdDestinationParametersViewModel @Inject constructor( ) : ScopedViewModel(savedStateHandle) { companion object { // The maximum number of characters allowed in a URL by Chrome - private const val MAX_CHARACTERS = 2083 + private const val MAX_CHARACTERS = 2096 } private val navArgs: BlazeCampaignCreationAdDestinationParametersFragmentArgs by savedStateHandle.navArgs() @@ -60,6 +61,6 @@ class BlazeCampaignCreationAdDestinationParametersViewModel @Inject constructor( } val charactersRemaining: Int - get() = MAX_CHARACTERS - url.length + get() = MAX_CHARACTERS - parameters.joinToString().length } } From dbfc254955383d7f2a5cb68e7747fe1d8221a76e Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 13 Feb 2024 11:22:11 +0100 Subject: [PATCH 063/119] Use mode of product details usage to have finite compile time defined set --- .../media/ProductImagesNotificationHandler.kt | 7 ++++- .../android/ui/main/MainActivity.kt | 7 +++-- .../AIProductDescriptionDialogFragment.kt | 5 ++- .../android/ui/mystore/MyStoreFragment.kt | 3 +- .../ui/products/ProductDetailFragment.kt | 11 +++++++ .../ui/products/ProductDetailViewModel.kt | 31 +++++++++++++------ .../ui/products/ProductListFragment.kt | 6 ++-- .../android/ui/products/ProductNavigator.kt | 2 +- .../products/ai/AddProductWithAIFragment.kt | 3 +- .../attributes/AttributesAddedFragment.kt | 6 ++-- .../main/res/navigation/nav_graph_main.xml | 9 ++---- .../res/navigation/nav_graph_products.xml | 9 ++---- 12 files changed, 63 insertions(+), 36 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/media/ProductImagesNotificationHandler.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/media/ProductImagesNotificationHandler.kt index 58b4fa08978..706df75c3b9 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/media/ProductImagesNotificationHandler.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/media/ProductImagesNotificationHandler.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.R import com.woocommerce.android.model.Product import com.woocommerce.android.ui.media.MediaFileUploadHandler.ProductImageUploadData import com.woocommerce.android.ui.media.MediaUploadErrorListFragmentArgs +import com.woocommerce.android.ui.products.ProductDetailFragment import com.woocommerce.android.ui.products.ProductDetailFragmentArgs import com.woocommerce.android.util.StringUtils import org.wordpress.android.util.SystemServiceFactory @@ -190,7 +191,11 @@ class ProductImagesNotificationHandler @Inject constructor( NavDeepLinkBuilder(context) .setGraph(R.navigation.nav_graph_main) .setDestination(R.id.productDetailFragment) - .setArguments(ProductDetailFragmentArgs(remoteProductId = productId).toBundle()) + .setArguments( + ProductDetailFragmentArgs( + mode = ProductDetailFragment.Mode.ShowProduct(productId) + ).toBundle() + ) .createPendingIntent() /** diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt index 1f30bc19d3a..9d3000f3475 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/main/MainActivity.kt @@ -101,6 +101,7 @@ import com.woocommerce.android.ui.plans.di.TrialStatusBarFormatterFactory import com.woocommerce.android.ui.plans.trial.DetermineTrialStatusBarState.TrialStatusBarState import com.woocommerce.android.ui.prefs.AppSettingsActivity import com.woocommerce.android.ui.prefs.RequestedAnalyticsValue +import com.woocommerce.android.ui.products.ProductDetailFragment import com.woocommerce.android.ui.products.ProductListFragmentDirections import com.woocommerce.android.ui.reviews.ReviewListFragmentDirections import com.woocommerce.android.util.ChromeCustomTabUtils @@ -946,7 +947,7 @@ class MainActivity : override fun showProductDetail(remoteProductId: Long, enableTrash: Boolean) { val action = NavGraphMainDirections.actionGlobalProductDetailFragment( - remoteProductId = remoteProductId, + mode = ProductDetailFragment.Mode.ShowProduct(remoteProductId), isTrashEnabled = enableTrash ) navController.navigateSafely(action) @@ -957,7 +958,7 @@ class MainActivity : val extras = FragmentNavigatorExtras(sharedView to productCardDetailTransitionName) val action = NavGraphMainDirections.actionGlobalProductDetailFragment( - remoteProductId = remoteProductId, + mode = ProductDetailFragment.Mode.ShowProduct(remoteProductId), isTrashEnabled = enableTrash ) navController.navigateSafely(directions = action, extras = extras) @@ -974,7 +975,7 @@ class MainActivity : override fun showAddProduct(imageUris: List) { showBottomNav() val action = NavGraphMainDirections.actionGlobalProductDetailFragment( - isAddProduct = true, + mode = ProductDetailFragment.Mode.AddNewProduct, images = imageUris.toTypedArray() ) navController.navigateSafely(action) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/AIProductDescriptionDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/AIProductDescriptionDialogFragment.kt index a8882e454fc..c238d859262 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/AIProductDescriptionDialogFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/AIProductDescriptionDialogFragment.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.R.style import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.mystore.AIProductDescriptionDialogViewModel.TryAIProductDescriptionGeneration +import com.woocommerce.android.ui.products.ProductDetailFragment import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.util.DisplayUtils @@ -59,7 +60,9 @@ class AIProductDescriptionDialogFragment : DialogFragment() { private fun openBlankProduct() { findNavController().navigateSafely( - NavGraphMainDirections.actionGlobalProductDetailFragment(isAddProduct = true) + NavGraphMainDirections.actionGlobalProductDetailFragment( + mode = ProductDetailFragment.Mode.AddNewProduct, + ) ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt index be3ea3d2742..0e265c537f4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt @@ -69,6 +69,7 @@ import com.woocommerce.android.ui.mystore.MyStoreViewModel.RevenueStatsViewState import com.woocommerce.android.ui.mystore.MyStoreViewModel.VisitorStatsViewState import com.woocommerce.android.ui.prefs.privacy.banner.PrivacyBannerFragmentDirections import com.woocommerce.android.ui.products.AddProductNavigator +import com.woocommerce.android.ui.products.ProductDetailFragment import com.woocommerce.android.util.ActivityUtils import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.DateUtils @@ -442,7 +443,7 @@ class MyStoreFragment : when (event) { is OpenTopPerformer -> findNavController().navigateSafely( NavGraphMainDirections.actionGlobalProductDetailFragment( - remoteProductId = event.productId, + mode = ProductDetailFragment.Mode.ShowProduct(event.productId), isTrashEnabled = false ) ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt index 638676f60ab..352bfdd69a5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt @@ -88,6 +88,7 @@ import com.woocommerce.android.widgets.SkeletonView import com.woocommerce.android.widgets.WCProductImageGalleryView.OnGalleryImageInteractionListener import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize import org.wordpress.android.util.ActivityUtils import javax.inject.Inject @@ -676,4 +677,14 @@ class ProductDetailFragment : } override fun getFragmentTitle(): String = productName + + @Parcelize + sealed class Mode : Parcelable { + @Parcelize + data object Loading: Mode() + @Parcelize + data class ShowProduct(val remoteProductId: Long): Mode() + @Parcelize + data object AddNewProduct: Mode() + } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt index 398ab875815..124ded6cb77 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt @@ -306,8 +306,8 @@ class ProductDetailViewModel @Inject constructor( /** * Returns boolean value of [navArgs.isAddProduct] to determine if the view model was started for the **add** flow */ - val isAddFlowEntryPoint: Boolean - get() = navArgs.isAddProduct + private val isAddFlowEntryPoint: Boolean + get() = navArgs.mode == ProductDetailFragment.Mode.AddNewProduct /** * Validates if the view model was started for the **add** flow AND there is an already valid product to modify. @@ -353,13 +353,16 @@ class ProductDetailViewModel @Inject constructor( } private fun initializeViewState() { - when (isAddFlowEntryPoint) { - true -> startAddNewProduct() - else -> { - loadRemoteProduct(navArgs.remoteProductId) + when (val mode = navArgs.mode) { + is ProductDetailFragment.Mode.AddNewProduct -> startAddNewProduct() + is ProductDetailFragment.Mode.ShowProduct -> { + loadRemoteProduct(mode.remoteProductId) if (navArgs.isAIContent && !appPrefsWrapper.isAiProductCreationSurveyDismissed) triggerEventWithDelay(ShowAiProductCreationSurveyBottomSheet, delay = 500) } + is ProductDetailFragment.Mode.Loading -> { + viewState = viewState.copy(isSkeletonShown = true) + } } } @@ -381,10 +384,17 @@ class ProductDetailViewModel @Inject constructor( private fun initializeStoredProductAfterRestoration() { launch { - storedProduct.value = if (isAddFlowEntryPoint && !isProductStoredAtSite) { - createDefaultProductForAddFlow() + if (isAddFlowEntryPoint && !isProductStoredAtSite) { + storedProduct.value = createDefaultProductForAddFlow() } else { - productRepository.getProductAsync(viewState.productDraft?.remoteId ?: navArgs.remoteProductId) + val mode = navArgs.mode + if (mode is ProductDetailFragment.Mode.ShowProduct) { + storedProduct.value = productRepository.getProductAsync( + viewState.productDraft?.remoteId ?: mode.remoteProductId + ) + } else { + viewState = viewState.copy(isSkeletonShown = true) + } } } } @@ -1340,7 +1350,8 @@ class ProductDetailViewModel @Inject constructor( fun refreshProduct() { launch { - fetchProduct(viewState.productDraft?.remoteId ?: navArgs.remoteProductId) + val mode = navArgs.mode as ProductDetailFragment.Mode.ShowProduct + fetchProduct(viewState.productDraft?.remoteId ?: mode.remoteProductId) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index b4e42db5060..e64b80e57d8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -128,7 +128,9 @@ class ProductListFragment : TabletLayoutSetupHelper.Screen.Navigation( childFragmentManager, R.navigation.nav_graph_products, - null, + ProductDetailFragmentArgs( + mode = ProductDetailFragment.Mode.Loading, + ).toBundle() ) } @@ -163,7 +165,7 @@ class ProductListFragment : tabletLayoutSetupHelper.onItemClicked( getTabletActionToNavigateTo = { NavGraphMainDirections.actionGlobalProductDetailFragment( - remoteProductId = id, + mode = ProductDetailFragment.Mode.ShowProduct(id), isTrashEnabled = true, ) }, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductNavigator.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductNavigator.kt index b422a29de80..b4ce630ba8d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductNavigator.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductNavigator.kt @@ -265,7 +265,7 @@ class ProductNavigator @Inject constructor() { is ProductNavigationTarget.ViewProductAdd -> { val directions = NavGraphMainDirections.actionGlobalProductDetailFragment( - isAddProduct = true, + mode = ProductDetailFragment.Mode.AddNewProduct, source = target.source ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/AddProductWithAIFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/AddProductWithAIFragment.kt index e081f0a0a1f..b2327ac7c7d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/AddProductWithAIFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ai/AddProductWithAIFragment.kt @@ -21,6 +21,7 @@ import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.base.UIMessageResolver import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus +import com.woocommerce.android.ui.products.ProductDetailFragment import com.woocommerce.android.ui.products.ai.AddProductWithAIViewModel.NavigateToProductDetailScreen import com.woocommerce.android.ui.products.ai.PackagePhotoViewModel.PackagePhotoData import com.woocommerce.android.ui.products.ai.ProductNameSubViewModel.NavigateToAIProductNameBottomSheet @@ -65,7 +66,7 @@ class AddProductWithAIFragment : BaseFragment(), MediaPickerResultHandler { is NavigateToAIProductNameBottomSheet -> navigateToAIProductName(event.initialName) is NavigateToProductDetailScreen -> findNavController().navigateSafely( directions = NavGraphMainDirections.actionGlobalProductDetailFragment( - remoteProductId = event.productId, + mode = ProductDetailFragment.Mode.ShowProduct(event.productId), isAIContent = true ), navOptions = navOptions { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/attributes/AttributesAddedFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/attributes/AttributesAddedFragment.kt index 4b823560188..bfb224516ff 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/attributes/AttributesAddedFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/attributes/AttributesAddedFragment.kt @@ -12,6 +12,7 @@ import com.woocommerce.android.extensions.handleDialogNotice import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.extensions.takeIfNotEqualTo import com.woocommerce.android.ui.products.BaseProductFragment +import com.woocommerce.android.ui.products.ProductDetailFragment import com.woocommerce.android.ui.products.ProductDetailViewModel.ProductExitEvent.ExitAttributesAdded import com.woocommerce.android.ui.products.variations.GenerateVariationBottomSheetFragment import com.woocommerce.android.ui.products.variations.GenerateVariationBottomSheetFragment.Companion.KEY_ADD_NEW_VARIATION @@ -88,8 +89,9 @@ class AttributesAddedFragment : when (event) { is ExitAttributesAdded -> AttributesAddedFragmentDirections - .actionAttributesAddedFragmentToProductDetailFragment() - .apply { findNavController().navigateSafely(this) } + .actionAttributesAddedFragmentToProductDetailFragment( + mode = ProductDetailFragment.Mode.AddNewProduct + ).apply { findNavController().navigateSafely(this) } is ShowSnackbar -> uiMessageResolver.getSnack(event.message) is ShowGenerateVariationConfirmation -> showGenerateVariationConfirmation(event.variationCandidates) is ShowGenerateVariationsError -> handleGenerateVariationError(event) diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index f9daa8b705a..e7f39d8635c 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -320,13 +320,8 @@ android:id="@+id/action_global_productDetailFragment" app:destination="@id/nav_graph_products"> - + android:name="mode" + app:argType="com.woocommerce.android.ui.products.ProductDetailFragment$Mode" /> - + android:name="mode" + app:argType="com.woocommerce.android.ui.products.ProductDetailFragment$Mode" /> Date: Tue, 13 Feb 2024 11:32:23 +0100 Subject: [PATCH 064/119] Fix for the layout --- WooCommerce/src/main/res/layout/fragment_product_list.xml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/res/layout/fragment_product_list.xml b/WooCommerce/src/main/res/layout/fragment_product_list.xml index 4b7db2a5d13..37518b73b03 100644 --- a/WooCommerce/src/main/res/layout/fragment_product_list.xml +++ b/WooCommerce/src/main/res/layout/fragment_product_list.xml @@ -132,11 +132,8 @@ android:id="@+id/detail_nav_container" android:layout_width="0dp" android:layout_height="match_parent" - android:visibility="gone" - app:defaultNavHost="false" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@+id/two_pane_layout_guideline" - app:layout_constraintTop_toTopOf="parent" - tools:visibility="visible" /> + app:layout_constraintStart_toEndOf="@+id/two_pane_layout_guideline" + app:layout_constraintTop_toTopOf="parent" /> From 216cd1a098ea0d3cc3677968ca29ebb7a63421eb Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 11:44:00 +0100 Subject: [PATCH 065/119] Make the getParameters more readable --- .../BlazeCampaignCreationAdDestinationViewModel.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt index 8338217d32a..8dcd72bbe2e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationViewModel.kt @@ -6,6 +6,7 @@ import com.woocommerce.android.R import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.util.getBaseUrl +import com.woocommerce.android.util.parseParameters import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import com.woocommerce.android.viewmodel.ResourceProvider @@ -31,7 +32,7 @@ class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( ViewState( productUrl = productUrl.trim('/'), siteUrl = selectedSite.get().url.trim('/'), - destinationUrl = navArgs.targetUrl.getBaseUrl(), + destinationUrl = navArgs.targetUrl.getBaseUrl() + "?a=b&c=d", parameters = getParameters(navArgs.targetUrl), isUrlDialogVisible = false ) @@ -70,10 +71,10 @@ class BlazeCampaignCreationAdDestinationViewModel @Inject constructor( } private fun getParameters(url: String): String { - return url.split("?") - .getOrNull(1) - ?.replace("&", "\n") - ?: return resourceProvider.getString(R.string.blaze_campaign_edit_ad_destination_empty_parameters_message) + return url.parseParameters().entries.joinToString(separator = "\n") + .ifBlank { + resourceProvider.getString(R.string.blaze_campaign_edit_ad_destination_empty_parameters_message) + } } private fun getTargetUrl(baseUrl: String, parameters: String): String { From 7c633026468fc3242c5cee131f99e86819b97be2 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 11:50:12 +0100 Subject: [PATCH 066/119] Remove the unneeded extension function --- .../BlazeCampaignCreationAdDestinationParametersViewModel.kt | 3 +-- .../src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt index dc8d816eb08..5c49494dfc1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/destination/BlazeCampaignCreationAdDestinationParametersViewModel.kt @@ -3,7 +3,6 @@ package com.woocommerce.android.ui.blaze.creation.destination import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import com.woocommerce.android.util.getBaseUrl -import com.woocommerce.android.util.joinToString import com.woocommerce.android.util.joinToUrl import com.woocommerce.android.util.parseParameters import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult @@ -61,6 +60,6 @@ class BlazeCampaignCreationAdDestinationParametersViewModel @Inject constructor( } val charactersRemaining: Int - get() = MAX_CHARACTERS - parameters.joinToString().length + get() = MAX_CHARACTERS - parameters.entries.joinToString("&").length } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt index 5a802e928c7..facfb1d4439 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/UrlUtils.kt @@ -49,7 +49,5 @@ fun String.parseParameters(): Map { fun Map.joinToUrl(baseUrl: String) = buildString { append(baseUrl) - appendWithIfNotEmpty(joinToString(), "?") + appendWithIfNotEmpty(entries.joinToString("&"), "?") } - -fun Map.joinToString() = entries.joinToString("&") { (key, value) -> "$key=$value" } From 61bb54cf1dfec21f8776af3eaf8c32966bb54caa Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 13 Feb 2024 11:53:14 +0100 Subject: [PATCH 067/119] Handle displaying success bottom sheet from BlazeCampaignPaymentSummaryFragment This approach simplifies thing a lot as it keeps the handling of displaying the success bottom sheet in a single place, the BlazeCampaignPaymentSummaryFragment. This also removes the need of a shared preference to keep track on when on when show the bottom sheet. --- .../kotlin/com/woocommerce/android/AppPrefs.kt | 13 +------------ .../com/woocommerce/android/AppPrefsWrapper.kt | 2 -- .../android/ui/blaze/MyStoreBlazeViewModel.kt | 8 -------- .../blaze/campaigs/BlazeCampaignListFragment.kt | 8 -------- .../blaze/campaigs/BlazeCampaignListViewModel.kt | 8 -------- .../BlazeCampaignPaymentSummaryFragment.kt | 14 +++++++++++--- .../BlazeCampaignPaymentSummaryViewModel.kt | 15 +++------------ .../BlazeCampaignSuccessBottomSheetFragment.kt | 6 ------ .../android/ui/mystore/MyStoreFragment.kt | 8 -------- .../android/ui/products/ProductDetailFragment.kt | 6 ------ .../android/ui/products/ProductDetailViewModel.kt | 8 -------- .../nav_graph_blaze_campaign_creation.xml | 7 +++++++ .../src/main/res/navigation/nav_graph_main.xml | 7 ------- 13 files changed, 22 insertions(+), 88 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt index d7bf58204f1..1d0b99920e3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -127,8 +127,7 @@ object AppPrefs { CREATED_STORE_THEME_ID, CHA_CHING_SOUND_ISSUE_DIALOG_DISMISSED, TIMES_AI_PRODUCT_CREATION_SURVEY_DISPLAYED, - AI_PRODUCT_CREATION_SURVEY_DISMISSED, - IS_COMING_FROM_BLAZE_CAMPAIGN_CREATIO_SUCCESS, + AI_PRODUCT_CREATION_SURVEY_DISMISSED } /** @@ -1083,16 +1082,6 @@ object AppPrefs { value = value ) - var isComingFromBlazeCampaignCreationSuccess: Boolean - get() = getBoolean( - key = DeletablePrefKey.IS_COMING_FROM_BLAZE_CAMPAIGN_CREATIO_SUCCESS, - default = false - ) - set(value) = setBoolean( - key = DeletablePrefKey.IS_COMING_FROM_BLAZE_CAMPAIGN_CREATIO_SUCCESS, - value = value - ) - fun incrementAIDescriptionTooltipShownNumber() { val currentTotal = getInt(DeletablePrefKey.NUMBER_OF_TIMES_AI_DESCRIPTION_TOOLTIP_SHOWN, 0) setInt(DeletablePrefKey.NUMBER_OF_TIMES_AI_DESCRIPTION_TOOLTIP_SHOWN, currentTotal + 1) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt index a487bd063ab..dc6cd251cf6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt @@ -42,8 +42,6 @@ class AppPrefsWrapper @Inject constructor() { var isAiProductCreationSurveyDismissed by AppPrefs::isAiProductCreationSurveyDismissed - var isComingFromBlazeCampaignCreationSuccess by AppPrefs::isComingFromBlazeCampaignCreationSuccess - fun getAppInstallationDate() = AppPrefs.installationDate fun getReceiptUrl(localSiteId: Int, remoteSiteId: Long, selfHostedSiteId: Long, orderId: Long) = diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/MyStoreBlazeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/MyStoreBlazeViewModel.kt index ff2aed45777..9306979e65b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/MyStoreBlazeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/MyStoreBlazeViewModel.kt @@ -185,12 +185,6 @@ class MyStoreBlazeViewModel @Inject constructor( ) } - fun checkBlazeCampaignCreationSuccess() { - if (prefsWrapper.isComingFromBlazeCampaignCreationSuccess) { - triggerEvent(ShowBlazeCampaignCreationSuccess) - } - } - sealed interface MyStoreBlazeCampaignState { object Hidden : MyStoreBlazeCampaignState data class NoCampaign( @@ -219,6 +213,4 @@ class MyStoreBlazeViewModel @Inject constructor( val url: String, val urlToTriggerExit: String ) : MultiLiveEvent.Event() - - object ShowBlazeCampaignCreationSuccess : MultiLiveEvent.Event() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt index 7a87265c7d6..e0ca507bc62 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt @@ -14,7 +14,6 @@ import com.woocommerce.android.R import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource -import com.woocommerce.android.ui.blaze.campaigs.BlazeCampaignListViewModel.ShowBlazeCampaignCreationSuccess import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus @@ -65,15 +64,8 @@ class BlazeCampaignListFragment : BaseFragment() { event.url, event.urlToTriggerExit ) - - is ShowBlazeCampaignCreationSuccess -> { - findNavController().navigateSafely( - NavGraphMainDirections.actionGlobalBlazeCampaignSuccessBottomSheetFragment() - ) - } } } - viewModel.checkBlazeCampaignCreationSuccess() } private fun openBlazeCreationFlow() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListViewModel.kt index c9710dda35f..518d86759f5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListViewModel.kt @@ -95,12 +95,6 @@ class BlazeCampaignListViewModel @Inject constructor( isCampaignCelebrationShown.value = false } - fun checkBlazeCampaignCreationSuccess() { - if (appPrefsWrapper.isComingFromBlazeCampaignCreationSuccess) { - triggerEvent(ShowBlazeCampaignCreationSuccess) - } - } - private suspend fun loadCampaignsFor(page: Int) { val result = blazeCampaignsStore.fetchBlazeCampaigns(selectedSite.get(), page) if (result.isError || result.model == null) { @@ -180,6 +174,4 @@ class BlazeCampaignListViewModel @Inject constructor( val url: String, val urlToTriggerExit: String ) : Event() - - object ShowBlazeCampaignCreationSuccess : Event() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt index 074f3306129..8b1432eb182 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt @@ -6,9 +6,11 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import androidx.navigation.navOptions import com.woocommerce.android.R +import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment -import com.woocommerce.android.ui.blaze.creation.payment.BlazeCampaignPaymentSummaryViewModel.NavigateToStartingScreen +import com.woocommerce.android.ui.blaze.creation.payment.BlazeCampaignPaymentSummaryViewModel.NavigateToStartingScreenWithSuccessBottomSheet import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus @@ -38,12 +40,18 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { MultiLiveEvent.Event.Exit -> findNavController().navigateUp() - is NavigateToStartingScreen -> navigateBackToStartingScreen() + is NavigateToStartingScreenWithSuccessBottomSheet -> navigateBackToStartingScreen() } } } private fun navigateBackToStartingScreen() { - findNavController().popBackStack(R.id.blazeCampaignCreationPreviewFragment, inclusive = true) + findNavController().navigateSafely( + directions = BlazeCampaignPaymentSummaryFragmentDirections + .actionBlazeCampaignPaymentSummaryFragmentToBlazeCampaignSuccessBottomSheetFragment(), + navOptions = navOptions { + popUpTo(R.id.blazeCampaignCreationPreviewFragment) { inclusive = true } + } + ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index 3c47e5bf8d0..53a17df8db2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -2,7 +2,6 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData -import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethodsData import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -16,8 +15,7 @@ import javax.inject.Inject @HiltViewModel class BlazeCampaignPaymentSummaryViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val blazeRepository: BlazeRepository, - private val appPrefsWrapper: AppPrefsWrapper + private val blazeRepository: BlazeRepository ) : ScopedViewModel(savedStateHandle) { private val navArgs = BlazeCampaignPaymentSummaryFragmentArgs.fromSavedStateHandle(savedStateHandle) @@ -38,10 +36,6 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( triggerEvent(MultiLiveEvent.Event.Exit) } - fun campaignCreatedSuccessfully() { - triggerEvent(NavigateToStartingScreen(campaignCreatedSuccessfully = true)) - } - private fun fetchPaymentMethodData() { paymentMethodState.value = PaymentMethodState.Loading launch { @@ -63,8 +57,7 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( } private fun onCampaignCreationSuccess() { - appPrefsWrapper.isComingFromBlazeCampaignCreationSuccess = true - triggerEvent(NavigateToStartingScreen(campaignCreatedSuccessfully = true)) + triggerEvent(NavigateToStartingScreenWithSuccessBottomSheet) } data class ViewState( @@ -84,7 +77,5 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( data class Error(val onRetry: () -> Unit) : PaymentMethodState } - data class NavigateToStartingScreen( - val campaignCreatedSuccessfully: Boolean - ) : MultiLiveEvent.Event() + object NavigateToStartingScreenWithSuccessBottomSheet : MultiLiveEvent.Event() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt index 7a1872b6846..552e2c308a1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/success/BlazeCampaignSuccessBottomSheetFragment.kt @@ -4,19 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.widgets.WCBottomSheetDialogFragment import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject @AndroidEntryPoint class BlazeCampaignSuccessBottomSheetFragment : WCBottomSheetDialogFragment() { - @Inject - lateinit var appPrefsWrapper: AppPrefsWrapper - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - appPrefsWrapper.isComingFromBlazeCampaignCreationSuccess = false return composeView { BlazeCampaignSuccessBottomSheet(::onDoneClicked) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt index 4de95e93772..be3ea3d2742 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt @@ -48,7 +48,6 @@ import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.blaze.MyStoreBlazeView import com.woocommerce.android.ui.blaze.MyStoreBlazeViewModel import com.woocommerce.android.ui.blaze.MyStoreBlazeViewModel.MyStoreBlazeCampaignState -import com.woocommerce.android.ui.blaze.MyStoreBlazeViewModel.ShowBlazeCampaignCreationSuccess import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.feedback.SurveyType @@ -274,15 +273,8 @@ class MyStoreFragment : ) ) } - - is ShowBlazeCampaignCreationSuccess -> { - findNavController().navigateSafely( - NavGraphMainDirections.actionGlobalBlazeCampaignSuccessBottomSheetFragment() - ) - } } } - myStoreBlazeViewModel.checkBlazeCampaignCreationSuccess() } private fun openBlazeCreationFlow(productId: Long?) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt index 9f20de714b5..9dac2ed4c73 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt @@ -170,7 +170,6 @@ class ProductDetailFragment : ) initializeViews(savedInstanceState) initializeViewModel() - viewModel.checkBlazeCampaignCreationSuccess() } override fun onDestroyView() { @@ -386,11 +385,6 @@ class ProductDetailFragment : ) is ShowAiProductCreationSurveyBottomSheet -> openAIProductCreationSurveyBottomSheet() - is ProductDetailViewModel.ShowBlazeCampaignCreationSuccess -> { - findNavController().navigateSafely( - NavGraphMainDirections.actionGlobalBlazeCampaignSuccessBottomSheetFragment() - ) - } else -> event.isHandled = false } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt index 2fb7d2ddf75..23ae378e719 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt @@ -2418,12 +2418,6 @@ class ProductDetailViewModel @Inject constructor( return storedProduct.value?.subscription?.length != viewState.productDraft?.subscription?.length } - fun checkBlazeCampaignCreationSuccess() { - if (appPrefsWrapper.isComingFromBlazeCampaignCreationSuccess) { - triggerEvent(ShowBlazeCampaignCreationSuccess) - } - } - /** * Sealed class that handles the back navigation for the product detail screens while providing a common * interface for managing them as a single type. Currently used in all the product sub detail screens when @@ -2473,8 +2467,6 @@ class ProductDetailViewModel @Inject constructor( object ShowAiProductCreationSurveyBottomSheet : Event() - object ShowBlazeCampaignCreationSuccess : Event() - /** * [productDraft] is used for the UI. Any updates to the fields in the UI would update this model. * [storedProduct.value] is the [Product] model that is fetched from the API and available in the local db. 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 b5fc72c39f6..e65e01174a3 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 @@ -135,5 +135,12 @@ + + diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index 91066854a65..8bf90eecda7 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -763,11 +763,4 @@ - - From 0f30c1783c34552f02bfca51546924782bb355f2 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 13 Feb 2024 11:57:19 +0100 Subject: [PATCH 068/119] Revert unnecessary file changes These changes were only generating noise in the PR --- .../src/main/kotlin/com/woocommerce/android/AppPrefs.kt | 2 +- .../android/ui/blaze/campaigs/BlazeCampaignListFragment.kt | 1 - .../creation/payment/BlazeCampaignPaymentSummaryViewModel.kt | 4 ---- .../woocommerce/android/ui/products/ProductDetailFragment.kt | 3 +-- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt index 1d0b99920e3..c6ad35944a0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -127,7 +127,7 @@ object AppPrefs { CREATED_STORE_THEME_ID, CHA_CHING_SOUND_ISSUE_DIALOG_DISMISSED, TIMES_AI_PRODUCT_CREATION_SURVEY_DISPLAYED, - AI_PRODUCT_CREATION_SURVEY_DISMISSED + AI_PRODUCT_CREATION_SURVEY_DISMISSED, } /** diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt index e0ca507bc62..8a65f50080d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt @@ -59,7 +59,6 @@ class BlazeCampaignListFragment : BaseFragment() { } else { openBlazeWebView(event.url, event.source) } - is BlazeCampaignListViewModel.ShowCampaignDetails -> openCampaignDetails( event.url, event.urlToTriggerExit diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index 53a17df8db2..057a3ca4a5c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -53,10 +53,6 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( fun onSubmitCampaign() { // TODO show loading and trigger campaign creation - onCampaignCreationSuccess() - } - - private fun onCampaignCreationSuccess() { triggerEvent(NavigateToStartingScreenWithSuccessBottomSheet) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt index 9dac2ed4c73..638676f60ab 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt @@ -385,7 +385,6 @@ class ProductDetailFragment : ) is ShowAiProductCreationSurveyBottomSheet -> openAIProductCreationSurveyBottomSheet() - else -> event.isHandled = false } } @@ -397,7 +396,7 @@ class ProductDetailFragment : ) } - fun showAIProductDescriptionBottomSheet(title: String, description: String?) { + private fun showAIProductDescriptionBottomSheet(title: String, description: String?) { findNavController().navigateSafely( ProductDetailFragmentDirections.actionProductDetailFragmentToAIProductDescriptionBottomSheetFragment( title, From 10bb801265f261e48fcd24b64a3f9abcd4b5bfe7 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 13 Feb 2024 12:11:29 +0100 Subject: [PATCH 069/119] Use nav_graph_blaze_campaign_creation In case the start destination changes in the future, better use the nav_graph id to ensure all screens related to campaign creation are "poppedUp" from the stack --- .../creation/payment/BlazeCampaignPaymentSummaryFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt index 8b1432eb182..ae2a8215c60 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt @@ -50,7 +50,7 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { directions = BlazeCampaignPaymentSummaryFragmentDirections .actionBlazeCampaignPaymentSummaryFragmentToBlazeCampaignSuccessBottomSheetFragment(), navOptions = navOptions { - popUpTo(R.id.blazeCampaignCreationPreviewFragment) { inclusive = true } + popUpTo(R.id.nav_graph_blaze_campaign_creation) { inclusive = true } } ) } From 663ddb5e759108d9d02afd60d87da1b49e9b510c Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 13 Feb 2024 03:33:04 -0800 Subject: [PATCH 070/119] Bump version number --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index 5ba55a703f1..a456732beb9 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -versionName=17.2 -versionCode=505 \ No newline at end of file +versionName=17.2.1 +versionCode=507 \ No newline at end of file From 0289b1039b570bc73fa5ef851033f4bc3d3eeb81 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 13:56:54 +0100 Subject: [PATCH 071/119] Add missing function call Co-authored-by: Jorge Mucientes --- .../payment/BlazeCampaignPaymentMethodsListViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt index 1e5b65894c0..523936e2f80 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentMethodsListViewModel.kt @@ -68,7 +68,7 @@ class BlazeCampaignPaymentMethodsListViewModel @Inject constructor( R.string.blaze_campaign_payment_added_successfully ) ) - MultiLiveEvent.Event.ExitWithResult(paymentMethodId) + triggerEvent(MultiLiveEvent.Event.ExitWithResult(paymentMethodId)) }, onFailure = { WooLog.e(WooLog.T.BLAZE, "Failed to extract payment method id from URL: $url", it) From cd871fa2ed3bf604858353903748212baab52d1d Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 13 Feb 2024 14:05:55 +0100 Subject: [PATCH 072/119] Navigate to graph --- .../android/ui/products/ProductListFragment.kt | 7 ++++--- .../android/util/TabletLayoutSetupHelper.kt | 14 ++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt index e64b80e57d8..8a4d7c6d1f7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductListFragment.kt @@ -163,11 +163,11 @@ class ProductListFragment : currencyFormatter = currencyFormatter, clickListener = { id, sharedView -> tabletLayoutSetupHelper.onItemClicked( - getTabletActionToNavigateTo = { - NavGraphMainDirections.actionGlobalProductDetailFragment( + tabletNavigateTo = { + R.id.nav_graph_products to ProductDetailFragmentArgs( mode = ProductDetailFragment.Mode.ShowProduct(id), isTrashEnabled = true, - ) + ).toBundle() }, navigateWithPhoneNavigation = { binding.addProductButton.hide() @@ -338,6 +338,7 @@ class ProductListFragment : enableSearchListeners() true } + R.id.menu_scan_barcode -> { AnalyticsTracker.track(AnalyticsEvent.PRODUCT_LIST_PRODUCT_BARCODE_SCANNING_TAPPED) ProductListFragmentDirections.actionProductListFragmentToScanToUpdateInventory().let { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt index 98764b599e3..a5848422192 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/TabletLayoutSetupHelper.kt @@ -7,10 +7,8 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner -import androidx.navigation.NavDirections import androidx.navigation.fragment.NavHostFragment import com.woocommerce.android.R -import com.woocommerce.android.extensions.navigateSafely import org.wordpress.android.util.DisplayUtils import javax.inject.Inject @@ -29,13 +27,17 @@ class TabletLayoutSetupHelper @Inject constructor( } fun onItemClicked( - getTabletActionToNavigateTo: () -> NavDirections, + tabletNavigateTo: () -> Pair, navigateWithPhoneNavigation: () -> Unit ) { if (FeatureFlag.BETTER_TABLETS_SUPPORT_PRODUCTS.isEnabled() && (DisplayUtils.isTablet(context) || DisplayUtils.isXLargeTablet(context)) ) { - navHostFragment.navController.navigateSafely(getTabletActionToNavigateTo()) + val navigationData = tabletNavigateTo() + navHostFragment.navController.navigate( + navigationData.first, + navigationData.second, + ) } else { navigateWithPhoneNavigation() } @@ -58,7 +60,7 @@ class TabletLayoutSetupHelper @Inject constructor( private fun initNavFragment(navigation: Screen.Navigation) { val fragmentManager = navigation.fragmentManager val navGraphId = navigation.navGraphId - val bundle = navigation.bundle + val bundle = navigation.initialBundle navHostFragment = NavHostFragment.create(navGraphId, bundle) @@ -94,7 +96,7 @@ class TabletLayoutSetupHelper @Inject constructor( data class Navigation( val fragmentManager: FragmentManager, val navGraphId: Int, - val bundle: Bundle? + val initialBundle: Bundle? ) } } From 2af5624d2aface4518268d6c186eb9377eba1ed9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 13 Feb 2024 14:17:53 +0100 Subject: [PATCH 073/119] reverted incorect change related to the skeleton showing --- .../woocommerce/android/ui/products/ProductDetailViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt index 124ded6cb77..269af1b5bc2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel.kt @@ -1362,7 +1362,6 @@ class ProductDetailViewModel @Inject constructor( } launch { - viewState = viewState.copy(isSkeletonShown = true) // fetch product val productInDb = productRepository.getProductAsync(remoteProductId) if (productInDb != null) { @@ -1375,6 +1374,7 @@ class ProductDetailViewModel @Inject constructor( fetchProductPassword(remoteProductId) } } else { + viewState = viewState.copy(isSkeletonShown = true) fetchProduct(remoteProductId) } viewState = viewState.copy(isSkeletonShown = false) From 6b20a8a13507dde58d0a038b82059a317fa19f5f Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 13 Feb 2024 14:21:47 +0100 Subject: [PATCH 074/119] Fixed formatting --- .../android/ui/products/ProductDetailFragment.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt index 352bfdd69a5..0a450b5d84d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt @@ -681,10 +681,12 @@ class ProductDetailFragment : @Parcelize sealed class Mode : Parcelable { @Parcelize - data object Loading: Mode() + data object Loading : Mode() + @Parcelize - data class ShowProduct(val remoteProductId: Long): Mode() + data class ShowProduct(val remoteProductId: Long) : Mode() + @Parcelize - data object AddNewProduct: Mode() + data object AddNewProduct : Mode() } } From 079462eca1bd950ccec1d81134766bc836e02697 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 13 Feb 2024 15:19:44 +0100 Subject: [PATCH 075/119] Fixed unit tests --- .../ProductDetailViewModelGenerateVariationFlowTest.kt | 2 +- .../android/ui/products/ProductDetailViewModelTest.kt | 8 +++++--- .../ui/products/ProductDetailViewModel_AddFlowTest.kt | 8 ++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModelGenerateVariationFlowTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModelGenerateVariationFlowTest.kt index 0f3b2eca05e..7b4ac7640d7 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModelGenerateVariationFlowTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModelGenerateVariationFlowTest.kt @@ -63,7 +63,7 @@ class ProductDetailViewModelGenerateVariationFlowTest : BaseUnitTest() { } private var savedState: SavedStateHandle = - ProductDetailFragmentArgs(remoteProductId = PRODUCT_REMOTE_ID, isAddProduct = false).toSavedStateHandle() + ProductDetailFragmentArgs(mode = ProductDetailFragment.Mode.ShowProduct(PRODUCT_REMOTE_ID)).toSavedStateHandle() private val parameterRepository: ParameterRepository = mock() private val generateVariationCandidates: GenerateVariationCandidates = mock() diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModelTest.kt index c3dff58111e..7372a4d0f4b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModelTest.kt @@ -108,7 +108,7 @@ class ProductDetailViewModelTest : BaseUnitTest() { } private var savedState: SavedStateHandle = - ProductDetailFragmentArgs(remoteProductId = PRODUCT_REMOTE_ID).toSavedStateHandle() + ProductDetailFragmentArgs(ProductDetailFragment.Mode.ShowProduct(PRODUCT_REMOTE_ID)).toSavedStateHandle() private val siteParams = SiteParameters( currencyCode = "USD", @@ -947,8 +947,10 @@ class ProductDetailViewModelTest : BaseUnitTest() { @Test fun `given image uris when app opened, then a product creation is triggered using the images`() = testBlocking { val uris = arrayOf("uri1", "uri2") - savedState = ProductDetailFragmentArgs(remoteProductId = PRODUCT_REMOTE_ID, images = uris) - .toSavedStateHandle() + savedState = ProductDetailFragmentArgs( + ProductDetailFragment.Mode.ShowProduct(PRODUCT_REMOTE_ID), + images = uris + ).toSavedStateHandle() doReturn(product).whenever(productRepository).getProductAsync(any()) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel_AddFlowTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel_AddFlowTest.kt index 5c4e7ae6705..9f659b18102 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel_AddFlowTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductDetailViewModel_AddFlowTest.kt @@ -86,7 +86,9 @@ class ProductDetailViewModel_AddFlowTest : BaseUnitTest() { onBlocking { invoke() } doReturn false } private var savedState: SavedStateHandle = - ProductDetailFragmentArgs(remoteProductId = PRODUCT_REMOTE_ID, isAddProduct = true).toSavedStateHandle() + ProductDetailFragmentArgs( + mode = ProductDetailFragment.Mode.AddNewProduct + ).toSavedStateHandle() private val siteParams = SiteParameters( currencyCode = "USD", @@ -399,7 +401,9 @@ class ProductDetailViewModel_AddFlowTest : BaseUnitTest() { fun `when a new product is saved, then assign the new id to ongoing image uploads`() = testBlocking { doReturn(Pair(true, PRODUCT_REMOTE_ID)).whenever(productRepository).addProduct(any()) doReturn(product).whenever(productRepository).getProductAsync(any()) - savedState = ProductDetailFragmentArgs(isAddProduct = true).toSavedStateHandle() + savedState = ProductDetailFragmentArgs( + mode = ProductDetailFragment.Mode.AddNewProduct + ).toSavedStateHandle() setup() viewModel.start() From 1b9970b2d5ba0a7e82858d62bece912592c94d9f Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 13 Feb 2024 07:46:34 -0800 Subject: [PATCH 076/119] Fix Fastfile variable --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 51561d8a8d6..b0505c8607b 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -412,7 +412,7 @@ platform :android do hotfix_version = release_version_current - UI.important("Pushing changes to remote and triggering hotfix build for version: #{version}") + UI.important("Pushing changes to remote and triggering hotfix build for version: #{hotfix_version}") unless options[:skip_confirm] || UI.confirm('Do you want to continue?') UI.user_error!("Terminating as requested. Don't forget to run the remainder of this automation manually.") end From bd4fb4f53a12b00505fde0ba6f2123ce8a3bf19e Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 17:10:27 +0100 Subject: [PATCH 077/119] Bump fluxc --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 385ed580859..3ddd2585de1 100644 --- a/build.gradle +++ b/build.gradle @@ -97,7 +97,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '2.66.0' + fluxCVersion = '2954-b6a6f03406478755900160b8dc52de6ea593986c' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From b19811140eece2ad8196d8172fa31fbf19e8ed79 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 17:10:41 +0100 Subject: [PATCH 078/119] Create a class that groups together all of the campaign details --- .../android/ui/blaze/BlazeRepository.kt | 36 +++- .../BlazeCampaignCreationPreviewViewModel.kt | 188 +++++++----------- 2 files changed, 107 insertions(+), 117 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 0072eaa097c..ec3269cbdc5 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 @@ -119,16 +119,28 @@ class BlazeRepository @Inject constructor( } } - suspend fun getCampaignPreviewDetails(productId: Long): CampaignPreview { + suspend fun generateDefaultCampaignDetails(productId: Long): CampaignDetails { + fun getDefaultBudget() = Budget( + totalBudget = DEFAULT_CAMPAIGN_DURATION * CAMPAIGN_MINIMUM_DAILY_SPEND, + spentBudget = 0f, + currencyCode = BLAZE_DEFAULT_CURRENCY_CODE, + durationInDays = DEFAULT_CAMPAIGN_DURATION, + startDate = Date().apply { time += 1.days.inWholeMilliseconds }, // By default start tomorrow + ) + val product = productDetailRepository.getProduct(productId) ?: productDetailRepository.fetchProductOrLoadFromCache(productId)!! - return CampaignPreview( + return CampaignDetails( productId = productId, userTimeZone = timezoneProvider.deviceTimezone.displayName, + tagLine = "", + description = "", targetUrl = product.permalink, - urlParams = null, - campaignImageUrl = product.firstImageUrl + urlParams = emptyMap(), + campaignImageUrl = product.firstImageUrl, + budget = getDefaultBudget(), + targetingParameters = TargetingParameters() ) } @@ -196,12 +208,24 @@ class BlazeRepository @Inject constructor( } @Parcelize - data class CampaignPreview( + data class CampaignDetails( val productId: Long, + val tagLine: String, + val description: String, val userTimeZone: String, val targetUrl: String, - val urlParams: Pair?, + val urlParams: Map, val campaignImageUrl: String?, + val budget: Budget, + val targetingParameters: TargetingParameters + ) : Parcelable + + @Parcelize + data class TargetingParameters( + val locations: List = emptyList(), + val languages: List = emptyList(), + val devices: List = emptyList(), + val interests: List = emptyList() ) : Parcelable @Parcelize 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 e5bb642f0f5..18a6656232e 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,19 +5,9 @@ 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 -import com.woocommerce.android.ui.blaze.BlazeRepository.CampaignPreview -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.Device -import com.woocommerce.android.ui.blaze.Interest -import com.woocommerce.android.ui.blaze.Language import com.woocommerce.android.ui.blaze.Location -import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.AdDetails -import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.AdDetailsUi.Loading import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.DEVICE import com.woocommerce.android.ui.blaze.creation.targets.BlazeTargetType.INTEREST @@ -26,16 +16,17 @@ 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.getNullableStateFlow 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.filterNotNull +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize -import java.util.Date import javax.inject.Inject -import kotlin.time.Duration.Companion.days @HiltViewModel class BlazeCampaignCreationPreviewViewModel @Inject constructor( @@ -45,66 +36,27 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( private val currencyFormatter: CurrencyFormatter ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationPreviewFragmentArgs by savedStateHandle.navArgs() - private suspend fun getCampaign() = blazeRepository.getCampaignPreviewDetails(navArgs.productId) - - private val adDetails = savedStateHandle.getStateFlow(viewModelScope, Loading) - private val budget = savedStateHandle.getStateFlow(viewModelScope, getDefaultBudget()) - - private val languages = blazeRepository.observeLanguages() - private val devices = blazeRepository.observeDevices() - private val interests = blazeRepository.observeInterests() - - private val selectedLanguageCodes = savedStateHandle.getStateFlow>( + private val campaignDetails = savedStateHandle.getNullableStateFlow( scope = viewModelScope, - initialValue = emptyList(), - key = "selectedLanguages" + key = "campaignDetails", + initialValue = null, + clazz = BlazeRepository.CampaignDetails::class.java ) - private val selectedLanguages = combine(languages, selectedLanguageCodes) { languages, selectedCodes -> - languages.filter { it.code in selectedCodes } - } + private val adDetailsState = savedStateHandle.getStateFlow(viewModelScope, AdDetailsUiState.LOADING) - private val selectedDeviceIds = savedStateHandle.getStateFlow>( - scope = viewModelScope, - initialValue = emptyList(), - key = "selectedDevices" - ) - - private val selectedDevices = combine(devices, selectedDeviceIds) { devices, selectedIds -> - devices.filter { it.id in selectedIds } - } - private val selectedInterestIds = savedStateHandle.getStateFlow>( - scope = viewModelScope, - initialValue = emptyList(), - key = "selectedInterests" - ) - - private val selectedInterests = combine(interests, selectedInterestIds) { interests, selectedIds -> - interests.filter { it.id in selectedIds } - } - - private val selectedLocations = savedStateHandle.getStateFlow>( - scope = viewModelScope, - initialValue = emptyList() - ) - - val viewState = combine( - adDetails, - budget, - selectedLanguages, - selectedDevices, - selectedInterests, - selectedLocations - ) { ad, budget, selectedLanguages, selectedDevices, selectedInterests, selectedLocations -> + val viewState = combine(campaignDetails.filterNotNull(), adDetailsState) { campaignDetails, adDetailsState -> CampaignPreviewUiState( - adDetails = ad, - campaignDetails = getCampaign().toCampaignDetailsUi( - budget, - selectedLanguages, - selectedDevices, - selectedInterests, - selectedLocations - ) + adDetails = when (adDetailsState) { + AdDetailsUiState.LOADING -> AdDetailsUi.Loading + AdDetailsUiState.LOADED -> AdDetailsUi.AdDetails( + productId = navArgs.productId, + description = campaignDetails.description, + tagLine = campaignDetails.tagLine, + campaignImageUrl = campaignDetails.campaignImageUrl + ) + }, + campaignDetails = campaignDetails.toCampaignDetailsUi() ) }.asLiveData() @@ -117,7 +69,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( } fun onEditAdClicked() { - (adDetails.value as? AdDetails)?.let { + campaignDetails.value?.let { triggerEvent( NavigateToEditAdScreen( productId = navArgs.productId, @@ -130,65 +82,80 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( } fun onAdUpdated(tagline: String, description: String, campaignImageUrl: String?) { - adDetails.update { - AdDetails( - productId = navArgs.productId, - description = description, + campaignDetails.update { + it?.copy( tagLine = tagline, + description = description, campaignImageUrl = campaignImageUrl ) } } - fun onBudgetAndDurationUpdated(updatedBudget: Budget) { - budget.update { updatedBudget } + fun onBudgetAndDurationUpdated(updatedBudget: BlazeRepository.Budget) { + campaignDetails.update { it?.copy(budget = updatedBudget) } } fun onTargetSelectionUpdated(targetType: BlazeTargetType, selectedIds: List) { launch { when (targetType) { - LANGUAGE -> selectedLanguageCodes.update { selectedIds } - DEVICE -> selectedDeviceIds.update { selectedIds } - INTEREST -> selectedInterestIds.update { selectedIds } + LANGUAGE -> blazeRepository.observeLanguages().first().let { languages -> + val selectedLanguages = languages.filter { selectedIds.contains(it.code) } + campaignDetails.update { + it?.copy(targetingParameters = it.targetingParameters.copy(languages = selectedLanguages)) + } + } + DEVICE -> blazeRepository.observeDevices().first().let { devices -> + val selectedDevices = devices.filter { selectedIds.contains(it.id) } + campaignDetails.update { + it?.copy(targetingParameters = it.targetingParameters.copy(devices = selectedDevices)) + } + } + INTEREST -> blazeRepository.observeInterests().first().let { interests -> + val selectedInterests = interests.filter { selectedIds.contains(it.id) } + campaignDetails.update { + it?.copy(targetingParameters = it.targetingParameters.copy(interests = selectedInterests)) + } + } else -> Unit } } } fun onTargetLocationsUpdated(locations: List) { - selectedLocations.update { locations } + campaignDetails.update { + it?.copy(targetingParameters = it.targetingParameters.copy(locations = locations)) + } } fun onConfirmClicked() { - triggerEvent(NavigateToPaymentSummary(budget.value)) + campaignDetails.value?.let { + triggerEvent(NavigateToPaymentSummary(it.budget)) + } } private fun loadData() { launch { + if (campaignDetails.value == null) { + launch { campaignDetails.value = blazeRepository.generateDefaultCampaignDetails(navArgs.productId) } + } + blazeRepository.fetchLanguages() blazeRepository.fetchDevices() blazeRepository.fetchInterests() blazeRepository.fetchAdSuggestions(navArgs.productId).getOrNull().let { suggestions -> - adDetails.update { - AdDetails( - productId = navArgs.productId, - description = suggestions?.firstOrNull()?.description ?: "", - tagLine = suggestions?.firstOrNull()?.tagLine ?: "", - campaignImageUrl = getCampaign().campaignImageUrl + adDetailsState.value = AdDetailsUiState.LOADED + campaignDetails.update { + it?.copy( + tagLine = suggestions?.firstOrNull()?.tagLine.orEmpty(), + description = suggestions?.firstOrNull()?.description.orEmpty() ) } } } } - private fun CampaignPreview.toCampaignDetailsUi( - budget: Budget, - languages: List, - devices: List, - interests: List, - locations: List - ) = CampaignDetailsUi( + private fun BlazeRepository.CampaignDetails.toCampaignDetailsUi() = CampaignDetailsUi( budget = CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_budget), displayValue = budget.toDisplayValue(), @@ -199,34 +166,36 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( targetDetails = listOf( CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_language), - displayValue = languages.joinToString { it.name } + displayValue = targetingParameters.languages.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, onItemSelected = { - triggerEvent(NavigateToTargetSelectionScreen(LANGUAGE, languages.map { it.code })) + triggerEvent(NavigateToTargetSelectionScreen( + targetType = LANGUAGE, + selectedIds = targetingParameters.languages.map { it.code })) }, ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_devices), - displayValue = devices.joinToString { it.name } + displayValue = targetingParameters.devices.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, onItemSelected = { - triggerEvent(NavigateToTargetSelectionScreen(DEVICE, devices.map { it.id })) + triggerEvent(NavigateToTargetSelectionScreen(DEVICE, targetingParameters.devices.map { it.id })) }, ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_location), - displayValue = locations.joinToString { it.name } + displayValue = targetingParameters.locations.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, onItemSelected = { - triggerEvent(NavigateToTargetLocationSelectionScreen(locations)) + triggerEvent(NavigateToTargetLocationSelectionScreen(targetingParameters.locations)) }, ), CampaignDetailItemUi( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_interests), - displayValue = interests.joinToString { it.description } + displayValue = targetingParameters.interests.joinToString { it.description } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, onItemSelected = { - triggerEvent(NavigateToTargetSelectionScreen(INTEREST, interests.map { it.id })) + triggerEvent(NavigateToTargetSelectionScreen(INTEREST, targetingParameters.interests.map { it.id })) }, ), ), @@ -240,7 +209,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ) ) - private fun Budget.toDisplayValue(): String { + private fun BlazeRepository.Budget.toDisplayValue(): String { val totalBudgetWithCurrency = currencyFormatter.formatCurrency( totalBudget.toBigDecimal(), currencyCode @@ -253,22 +222,19 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( return "$totalBudgetWithCurrency, $duration" } - private fun getDefaultBudget() = Budget( - totalBudget = DEFAULT_CAMPAIGN_DURATION * CAMPAIGN_MINIMUM_DAILY_SPEND, - spentBudget = 0f, - currencyCode = BlazeRepository.BLAZE_DEFAULT_CURRENCY_CODE, - durationInDays = DEFAULT_CAMPAIGN_DURATION, - startDate = Date().apply { time += 1.days.inWholeMilliseconds }, // By default start tomorrow - ) - data class CampaignPreviewUiState( val adDetails: AdDetailsUi, val campaignDetails: CampaignDetailsUi, ) + enum class AdDetailsUiState { + LOADING, + LOADED + } + sealed interface AdDetailsUi : Parcelable { @Parcelize - object Loading : AdDetailsUi + data object Loading : AdDetailsUi @Parcelize data class AdDetails( @@ -293,7 +259,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ) data class NavigateToBudgetScreen( - val budget: Budget + val budget: BlazeRepository.Budget ) : MultiLiveEvent.Event() data class NavigateToAdDestinationScreen( From a62cb00740087f798fbb25039e836643b4ac3c44 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 17:34:00 +0100 Subject: [PATCH 079/119] Add Repository logic for creating campaigns --- .../android/ui/blaze/BlazeRepository.kt | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 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 ec3269cbdc5..183cbe365a2 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 @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.blaze import android.os.Parcelable +import com.woocommerce.android.BuildConfig import com.woocommerce.android.OnChangedException import com.woocommerce.android.model.CreditCardType import com.woocommerce.android.tools.SelectedSite @@ -9,9 +10,13 @@ import com.woocommerce.android.util.TimezoneProvider import com.woocommerce.android.util.WooLog import kotlinx.coroutines.flow.map import kotlinx.parcelize.Parcelize +import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.blaze.BlazeAdForecast import org.wordpress.android.fluxc.model.blaze.BlazeAdSuggestion +import org.wordpress.android.fluxc.model.blaze.BlazeCampaignCreationRequest +import org.wordpress.android.fluxc.model.blaze.BlazeCampaignType import org.wordpress.android.fluxc.model.blaze.BlazePaymentMethod.PaymentMethodInfo +import org.wordpress.android.fluxc.model.blaze.BlazeTargetingParameters import org.wordpress.android.fluxc.store.blaze.BlazeCampaignsStore import java.util.Date import javax.inject.Inject @@ -27,8 +32,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 = 5F // USD - const val CAMPAIGN_MAXIMUM_DAILY_SPEND = 50F // USD + const val CAMPAIGN_MINIMUM_DAILY_SPEND = 5.0 // USD + const val CAMPAIGN_MAXIMUM_DAILY_SPEND = 50.0 // USD const val CAMPAIGN_MAX_DURATION = 28 // Days } @@ -122,7 +127,7 @@ class BlazeRepository @Inject constructor( suspend fun generateDefaultCampaignDetails(productId: Long): CampaignDetails { fun getDefaultBudget() = Budget( totalBudget = DEFAULT_CAMPAIGN_DURATION * CAMPAIGN_MINIMUM_DAILY_SPEND, - spentBudget = 0f, + spentBudget = 0.0, currencyCode = BLAZE_DEFAULT_CURRENCY_CODE, durationInDays = DEFAULT_CAMPAIGN_DURATION, startDate = Date().apply { time += 1.days.inWholeMilliseconds }, // By default start tomorrow @@ -207,6 +212,55 @@ class BlazeRepository @Inject constructor( } } + suspend fun createCampaign( + campaignDetails: CampaignDetails, + paymentMethodId: String + ): Result { + val image = prepareCampaignImage().getOrElse { + return Result.failure(it) + } + + val result = blazeCampaignsStore.createCampaign( + selectedSite.get(), + request = BlazeCampaignCreationRequest( + origin = "wc-android", + originVersion = BuildConfig.VERSION_NAME, + type = BlazeCampaignType.PRODUCT, + paymentMethodId = paymentMethodId, + targetResourceId = campaignDetails.productId, + tagLine = campaignDetails.tagLine, + description = campaignDetails.description, + startDate = campaignDetails.budget.startDate, + endDate = campaignDetails.budget.endDate, + budget = campaignDetails.budget.totalBudget, + targetUrl = campaignDetails.targetUrl, + urlParams = campaignDetails.urlParams, + mainImage = image, + targetingParameters = campaignDetails.targetingParameters.let { + BlazeTargetingParameters( + locations = it.locations.map { location -> location.id.toString() }, + languages = it.languages.map { language -> language.code }, + devices = it.devices.map { device -> device.id }, + topics = it.interests.map { interest -> interest.id } + ) + } + ) + ) + + return when { + result.isError -> { + WooLog.w(WooLog.T.BLAZE, "Failed to create campaign: ${result.error}") + Result.failure(OnChangedException(result.error)) + } + + else -> Result.success(Unit) + } + } + + private fun prepareCampaignImage(): Result { + TODO() + } + @Parcelize data class CampaignDetails( val productId: Long, @@ -236,12 +290,15 @@ class BlazeRepository @Inject constructor( @Parcelize data class Budget( - val totalBudget: Float, - val spentBudget: Float, + val totalBudget: Double, + val spentBudget: Double, val currencyCode: String, val durationInDays: Int, val startDate: Date, - ) : Parcelable + ) : Parcelable { + val endDate: Date + get() = Date(startDate.time + durationInDays.days.inWholeMilliseconds) + } @Parcelize data class PaymentMethodsData( From 705000cb0ad88ba48299f0eef22f83e1b3ac4de9 Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Tue, 13 Feb 2024 08:38:44 -0800 Subject: [PATCH 080/119] Revert navigation version --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index cf0dbbdfb44..1912ba4f54f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ pluginManagement { gradle.ext.detektVersion = '1.19.0' gradle.ext.kotlinVersion = '1.9.22' gradle.ext.measureBuildsVersion = '2.0.3' - gradle.ext.navigationVersion = '2.7.7' + gradle.ext.navigationVersion = '2.5.3' gradle.ext.sentryVersion = '3.5.0' gradle.ext.violationCommentsVersion = '1.69.0' From 1cf0e646c444a7aa907ac4ea10d08b80c6b94ddd Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 17:40:09 +0100 Subject: [PATCH 081/119] Update Compose BOM, Accompanist and compiler version --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 385ed580859..1f391efc4bc 100644 --- a/build.gradle +++ b/build.gradle @@ -132,9 +132,9 @@ ext { httpClientAndroidVersion = '4.3.5.1' // Compose and its module versions need to be consistent with each other (for example 'compose-theme-adapter') - composeBOMVersion = "2023.06.01" - composeCompilerVersion = "1.5.8" - composeAccompanistVersion = "0.23.1" + composeBOMVersion = "2023.10.01" + composeCompilerVersion = "1.5.9" + composeAccompanistVersion = "0.32.0" // Testing jUnitVersion = '4.13.2' From e46aa696f734ba8a08b11d5a718e529793e7434e Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 17:41:21 +0100 Subject: [PATCH 082/119] Replace deprecated "with()" with "togetherwith()" --- .../android/ui/blaze/BlazeCampaignCreationScreen.kt | 4 ++-- .../ui/login/jetpack/main/JetpackActivationMainScreen.kt | 4 ++-- .../hub/depositsummary/PaymentsHubDepositSummaryView.kt | 8 ++++---- .../android/ui/shipping/InstallWCShippingScreen.kt | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt index f2e4f646fa9..429ddcd1d96 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/BlazeCampaignCreationScreen.kt @@ -7,7 +7,7 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.animation.with +import androidx.compose.animation.togetherWith import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -84,7 +84,7 @@ fun BlazeCampaignCreationScreen( ) { paddingValues -> AnimatedContent( targetState = viewState, - transitionSpec = { fadeIn() with fadeOut() } + transitionSpec = { fadeIn() togetherWith fadeOut() } ) { targetState -> when (targetState) { is BlazeCreationViewState.Intro -> BlazeCreationIntroScreen( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt index daa3415069c..cdf641ec382 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt @@ -11,7 +11,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically -import androidx.compose.animation.with +import androidx.compose.animation.togetherWith import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -109,7 +109,7 @@ fun JetpackActivationMainScreen( transition.AnimatedContent( contentKey = { it is JetpackActivationMainViewModel.ViewState.ErrorViewState }, transitionSpec = { - fadeIn(animationSpec = tween(DefaultDurationMillis, delayMillis = DefaultDurationMillis)) with + fadeIn(animationSpec = tween(DefaultDurationMillis, delayMillis = DefaultDurationMillis)) togetherWith fadeOut(animationSpec = tween(DefaultDurationMillis)) }, modifier = Modifier.weight(1f) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt index ce8465b8c54..cf651c252db 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt @@ -17,7 +17,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically -import androidx.compose.animation.with +import androidx.compose.animation.togetherWith import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -558,11 +558,11 @@ private fun FundsNumber( targetState = valueToDisplay to valueAmount, transitionSpec = { if (animationPlayed) { - EnterTransition.None with ExitTransition.None + EnterTransition.None togetherWith ExitTransition.None } else if (targetState.second > initialState.second) { - slideInVertically { -it } with slideOutVertically { it } + slideInVertically { -it } togetherWith slideOutVertically { it } } else { - slideInVertically { it } with slideOutVertically { -it } + slideInVertically { it } togetherWith slideOutVertically { -it } } }, label = "AnimatedFundsNumber" diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt index 472c1008cc2..5a74ea52c59 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/shipping/InstallWCShippingScreen.kt @@ -11,7 +11,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.animation.with +import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.material.MaterialTheme @@ -49,10 +49,10 @@ fun InstallWCShippingScreen(viewState: ViewState) { // Apply a fade-in/fade-out globally, // then each child will animate the individual components separately fadeIn(tween(500, delayMillis = 500)) - .with(fadeOut(tween(500, easing = LinearOutSlowInEasing))) + .togetherWith(fadeOut(tween(500, easing = LinearOutSlowInEasing))) } else { // No-op animation, each screen will define animations for specific components separately - EnterTransition.None.with(ExitTransition.None) + EnterTransition.None.togetherWith(ExitTransition.None) } } ) { targetState -> From 8c0791d0c9b22f417716154a539ddc616a1b62bf Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 17:42:06 +0100 Subject: [PATCH 083/119] Update the pager parameters --- .../hub/depositsummary/PaymentsHubDepositSummaryView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt index cf651c252db..329c382e674 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/hub/depositsummary/PaymentsHubDepositSummaryView.kt @@ -138,7 +138,7 @@ fun PaymentsHubDepositSummaryView( .fillMaxWidth() .background(colorResource(id = R.color.color_surface)) ) { - val pagerState = rememberPagerState(initialPage = selectedPage) + val pagerState = rememberPagerState(initialPage = selectedPage, pageCount = { pageCount }) val isInitialLoad = remember { mutableStateOf(true) } val currencies = overview.infoPerCurrency.keys.toList() @@ -164,7 +164,6 @@ fun PaymentsHubDepositSummaryView( } HorizontalPager( - pageCount = pageCount, state = pagerState ) { pageIndex -> Column( From 2a30e23994d6b0d7d661a863149e30d1decf60c9 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 17:42:55 +0100 Subject: [PATCH 084/119] Replace the deprecated SwipeRefreshLayout with pullRefresh modifier in InboxScreen --- .../android/ui/inbox/InboxScreen.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/inbox/InboxScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/inbox/InboxScreen.kt index d11a0f1c537..32c63c5022e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/inbox/InboxScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/inbox/InboxScreen.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.inbox import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -19,10 +20,14 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Divider +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedButton import androidx.compose.material.Text import androidx.compose.material.TextButton +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -38,9 +43,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.core.text.HtmlCompat -import com.google.accompanist.swiperefresh.SwipeRefresh -import com.google.accompanist.swiperefresh.SwipeRefreshIndicator -import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.woocommerce.android.R import com.woocommerce.android.ui.compose.animations.SkeletonView import com.woocommerce.android.ui.compose.toAnnotatedString @@ -103,27 +105,19 @@ fun InboxEmptyCase() { } } +@OptIn(ExperimentalMaterialApi::class) @Composable fun InboxNotes( notes: List, isRefreshing: Boolean, onRefresh: () -> Unit ) { - SwipeRefresh( - state = rememberSwipeRefreshState(isRefreshing), - onRefresh = { onRefresh.invoke() }, - indicator = { state, trigger -> - SwipeRefreshIndicator( - state = state, - refreshTriggerDistance = trigger, - contentColor = MaterialTheme.colors.primary, - ) - } - ) { + val pullRefreshState = rememberPullRefreshState(isRefreshing, { onRefresh() }) + Box(Modifier.pullRefresh(pullRefreshState)) { if (notes.isEmpty()) { InboxEmptyCase() } else { - LazyColumn { + LazyColumn(Modifier.fillMaxSize()) { itemsIndexed(notes) { index, note -> InboxNoteRow(note = note) if (index < notes.lastIndex) @@ -134,6 +128,12 @@ fun InboxNotes( } } } + PullRefreshIndicator( + refreshing = isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + contentColor = MaterialTheme.colors.primary, + ) } } From c841d32447d425f9b897812483e76a6b7c6f4121 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 17:51:31 +0100 Subject: [PATCH 085/119] Introduce a safe type for campaign images This will allow us to keep track of the IDs of remote images. --- .../android/ui/blaze/BlazeRepository.kt | 34 ++++++++++++++----- .../ad/BlazeCampaignCreationEditAdFragment.kt | 8 ++--- .../ad/BlazeCampaignCreationEditAdScreen.kt | 5 +-- .../BlazeCampaignCreationEditAdViewModel.kt | 23 +++++++++---- .../BlazeCampaignCreationPreviewFragment.kt | 10 +++--- .../BlazeCampaignCreationPreviewViewModel.kt | 10 +++--- .../nav_graph_blaze_campaign_creation.xml | 6 ++-- 7 files changed, 60 insertions(+), 36 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 183cbe365a2..39d05828ba0 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,8 +32,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 = 5.0 // USD - const val CAMPAIGN_MAXIMUM_DAILY_SPEND = 50.0 // USD + const val CAMPAIGN_MINIMUM_DAILY_SPEND = 5f // USD + const val CAMPAIGN_MAXIMUM_DAILY_SPEND = 50f // USD const val CAMPAIGN_MAX_DURATION = 28 // Days } @@ -127,7 +127,7 @@ class BlazeRepository @Inject constructor( suspend fun generateDefaultCampaignDetails(productId: Long): CampaignDetails { fun getDefaultBudget() = Budget( totalBudget = DEFAULT_CAMPAIGN_DURATION * CAMPAIGN_MINIMUM_DAILY_SPEND, - spentBudget = 0.0, + spentBudget = 0f, currencyCode = BLAZE_DEFAULT_CURRENCY_CODE, durationInDays = DEFAULT_CAMPAIGN_DURATION, startDate = Date().apply { time += 1.days.inWholeMilliseconds }, // By default start tomorrow @@ -143,7 +143,10 @@ class BlazeRepository @Inject constructor( description = "", targetUrl = product.permalink, urlParams = emptyMap(), - campaignImageUrl = product.firstImageUrl, + campaignImage = product.images.firstOrNull().let { + if (it != null) BlazeCampaignImage.RemoteImage(it.id, it.source) + else BlazeCampaignImage.None + }, budget = getDefaultBudget(), targetingParameters = TargetingParameters() ) @@ -232,7 +235,7 @@ class BlazeRepository @Inject constructor( description = campaignDetails.description, startDate = campaignDetails.budget.startDate, endDate = campaignDetails.budget.endDate, - budget = campaignDetails.budget.totalBudget, + budget = campaignDetails.budget.totalBudget.toDouble(), targetUrl = campaignDetails.targetUrl, urlParams = campaignDetails.urlParams, mainImage = image, @@ -269,11 +272,26 @@ class BlazeRepository @Inject constructor( val userTimeZone: String, val targetUrl: String, val urlParams: Map, - val campaignImageUrl: String?, + val campaignImage: BlazeCampaignImage, val budget: Budget, val targetingParameters: TargetingParameters ) : Parcelable + sealed interface BlazeCampaignImage : Parcelable { + val uri: String + @Parcelize + data object None : BlazeCampaignImage { + override val uri: String + get() = "" + } + + @Parcelize + data class LocalImage(override val uri: String) : BlazeCampaignImage + + @Parcelize + data class RemoteImage(val mediaId: Long, override val uri: String) : BlazeCampaignImage + } + @Parcelize data class TargetingParameters( val locations: List = emptyList(), @@ -290,8 +308,8 @@ class BlazeRepository @Inject constructor( @Parcelize data class Budget( - val totalBudget: Double, - val spentBudget: Double, + val totalBudget: Float, + val spentBudget: Float, val currencyCode: String, val durationInDays: Int, val startDate: Date, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt index 7e0fe7efbeb..ef94c3eaf3f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdFragment.kt @@ -55,17 +55,13 @@ class BlazeCampaignCreationEditAdFragment : BaseFragment(), MediaPickerResultHan override fun onDeviceMediaSelected(imageUris: List, source: String) { if (imageUris.isNotEmpty()) { - onImageSelected(imageUris.first().toString()) + viewModel.onLocalImageSelected(imageUris.first().toString()) } } override fun onWPMediaSelected(images: List) { if (images.isNotEmpty()) { - onImageSelected(images.first().source) + viewModel.onWPMediaSelected(images.first()) } } - - private fun onImageSelected(mediaUri: String) { - viewModel.onImageChanged(mediaUri) - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt index ac685141b45..c8af2a1f939 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt @@ -48,6 +48,7 @@ import com.woocommerce.android.R.dimen import com.woocommerce.android.R.drawable import com.woocommerce.android.R.string import com.woocommerce.android.mediapicker.MediaPickerDialog +import com.woocommerce.android.ui.blaze.BlazeRepository.BlazeCampaignImage import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdViewModel.ViewState import com.woocommerce.android.ui.compose.component.Toolbar import com.woocommerce.android.ui.compose.component.WCOutlinedTextField @@ -295,7 +296,7 @@ private fun AdImageSection(viewState: ViewState, onChangeImageTapped: () -> Unit ) { SubcomposeAsyncImage( model = Builder(LocalContext.current) - .data(viewState.adImageUrl) + .data(viewState.adImage.uri) .crossfade(true) .fallback(drawable.blaze_campaign_product_placeholder) .placeholder(drawable.blaze_campaign_product_placeholder) @@ -365,7 +366,7 @@ fun PreviewCampaignEditAdContent() { WooThemeWithBackground { CampaignEditAdContent( viewState = ViewState( - adImageUrl = "https://rb.gy/gmjuwb" + adImage = BlazeCampaignImage.RemoteImage(0,"https://rb.gy/gmjuwb") ), onTagLineChanged = { }, onDescriptionChanged = { }, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt index 8236ecc09d3..bd3da3bbaf0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdViewModel.kt @@ -4,6 +4,7 @@ import android.os.Parcelable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope +import com.woocommerce.android.model.Product import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.AiSuggestionForAd import com.woocommerce.android.viewmodel.MultiLiveEvent.Event @@ -33,7 +34,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( private val _viewState = savedStateHandle.getStateFlow( scope = viewModelScope, - initialValue = ViewState(navArgs.adImageUrl) + initialValue = ViewState(navArgs.adImage) ) val viewState = _viewState.asLiveData() @@ -85,7 +86,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( EditAdResult( tagline = _viewState.value.tagLine, description = _viewState.value.description, - campaignImageUrl = _viewState.value.adImageUrl + campaignImage = _viewState.value.adImage ) ) ) @@ -116,9 +117,19 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( updateSuggestion(AiSuggestionForAd(_viewState.value.tagLine, description.take(TAGLINE_MAX_LENGTH))) } - fun onImageChanged(url: String) { + fun onLocalImageSelected(uri: String) { _viewState.update { - _viewState.value.copy(adImageUrl = url) + _viewState.value.copy(adImage = BlazeRepository.BlazeCampaignImage.LocalImage(uri)) + } + } + + fun onWPMediaSelected(image: Product.Image) { + _viewState.update { + _viewState.value.copy( + adImage = BlazeRepository.BlazeCampaignImage.RemoteImage( + mediaId = image.id, uri = image.source + ) + ) } } @@ -140,7 +151,7 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( @Parcelize data class ViewState( - val adImageUrl: String?, + val adImage: BlazeRepository.BlazeCampaignImage, val suggestions: List = emptyList(), val suggestionIndex: Int = 0, val isMediaPickerDialogVisible: Boolean = false @@ -163,6 +174,6 @@ class BlazeCampaignCreationEditAdViewModel @Inject constructor( data class EditAdResult( val tagline: String, val description: String, - val campaignImageUrl: String? + val campaignImage: BlazeRepository.BlazeCampaignImage ) : 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 de79c3f177b..814d45d1167 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 @@ -59,10 +59,10 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { is NavigateToEditAdScreen -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignCreationEditAdFragment( - event.productId, - event.tagLine, - event.description, - event.campaignImageUrl + productId = event.productId, + tagline = event.tagLine, + description = event.description, + adImage = event.campaignImage ) ) @@ -100,7 +100,7 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { private fun handleResults() { handleResult(BlazeCampaignCreationEditAdFragment.EDIT_AD_RESULT) { - viewModel.onAdUpdated(it.tagline, it.description, it.campaignImageUrl) + viewModel.onAdUpdated(it.tagline, it.description, it.campaignImage) } handleResult(BlazeCampaignBudgetFragment.EDIT_BUDGET_AND_DURATION_RESULT) { viewModel.onBudgetAndDurationUpdated(it) 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 18a6656232e..cfd5f05f326 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 @@ -53,7 +53,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( productId = navArgs.productId, description = campaignDetails.description, tagLine = campaignDetails.tagLine, - campaignImageUrl = campaignDetails.campaignImageUrl + campaignImageUrl = campaignDetails.campaignImage.uri ) }, campaignDetails = campaignDetails.toCampaignDetailsUi() @@ -75,18 +75,18 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( productId = navArgs.productId, tagLine = it.tagLine, description = it.description, - campaignImageUrl = it.campaignImageUrl + campaignImage = it.campaignImage ) ) } } - fun onAdUpdated(tagline: String, description: String, campaignImageUrl: String?) { + fun onAdUpdated(tagline: String, description: String, campaignImage: BlazeRepository.BlazeCampaignImage) { campaignDetails.update { it?.copy( tagLine = tagline, description = description, - campaignImageUrl = campaignImageUrl + campaignImage = campaignImage ) } } @@ -280,7 +280,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( val productId: Long, val tagLine: String, val description: String, - val campaignImageUrl: String? + val campaignImage: BlazeRepository.BlazeCampaignImage ) : MultiLiveEvent.Event() // TODO we need to pass more details to use in the campaign creation 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 2c1aabd5eb2..8d5744bbd7e 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 @@ -85,10 +85,8 @@ android:defaultValue="" app:argType="string" /> + android:name="adImage" + app:argType="com.woocommerce.android.ui.blaze.BlazeRepository$BlazeCampaignImage" /> Date: Tue, 13 Feb 2024 17:51:52 +0100 Subject: [PATCH 086/119] Replace the deprecated SwipeRefreshLayout with pullRefresh modifier in CouponListScreen --- .../android/ui/coupons/CouponListScreen.kt | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/CouponListScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/CouponListScreen.kt index 8f7fc8cac2d..3945bfb1ea3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/CouponListScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/CouponListScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -17,8 +18,12 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Divider +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -31,14 +36,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import com.google.accompanist.swiperefresh.SwipeRefresh -import com.google.accompanist.swiperefresh.SwipeRefreshIndicator -import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.woocommerce.android.R import com.woocommerce.android.ui.compose.animations.SkeletonView import com.woocommerce.android.ui.compose.component.InfiniteListHandler import com.woocommerce.android.ui.coupons.CouponListViewModel.CouponListState import com.woocommerce.android.ui.coupons.CouponListViewModel.LoadingState +import com.woocommerce.android.ui.coupons.CouponListViewModel.LoadingState.Appending +import com.woocommerce.android.ui.coupons.CouponListViewModel.LoadingState.Refreshing import com.woocommerce.android.ui.coupons.components.CouponExpirationLabel @Composable @@ -100,6 +104,7 @@ private fun EmptyCouponList() { } } +@OptIn(ExperimentalMaterialApi::class) @Composable private fun CouponList( coupons: List, @@ -108,17 +113,9 @@ private fun CouponList( onRefresh: () -> Unit, onLoadMore: () -> Unit ) { - SwipeRefresh( - state = rememberSwipeRefreshState(isRefreshing = loadingState == LoadingState.Refreshing), - onRefresh = onRefresh, - indicator = { state, refreshTrigger -> - SwipeRefreshIndicator( - state = state, - refreshTriggerDistance = refreshTrigger, - contentColor = MaterialTheme.colors.primary, - ) - } - ) { + val isRefreshing = loadingState == Refreshing + val pullRefreshState = rememberPullRefreshState(isRefreshing, { onRefresh() }) + Box(Modifier.pullRefresh(pullRefreshState)) { val listState = rememberLazyListState() LazyColumn( state = listState, @@ -136,7 +133,7 @@ private fun CouponList( thickness = dimensionResource(id = R.dimen.minor_10) ) } - if (loadingState == LoadingState.Appending) { + if (loadingState == Appending) { item { CircularProgressIndicator( modifier = Modifier @@ -151,6 +148,13 @@ private fun CouponList( InfiniteListHandler(listState = listState) { onLoadMore() } + + PullRefreshIndicator( + refreshing = isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + contentColor = MaterialTheme.colors.primary, + ) } } From 8a15a32fb4066d6e060993d6f2e7cae398f461ca Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 17:57:01 +0100 Subject: [PATCH 087/119] Replace the deprecated SwipeRefreshLayout with pullRefresh modifier in CouponSelectorScreen --- .../coupons/selector/CouponSelectorScreen.kt | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/selector/CouponSelectorScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/selector/CouponSelectorScreen.kt index 35458ddaaf3..200be535645 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/selector/CouponSelectorScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/selector/CouponSelectorScreen.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.coupons.selector import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -16,8 +17,12 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Divider +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.ui.Alignment @@ -28,14 +33,12 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import com.google.accompanist.swiperefresh.SwipeRefresh -import com.google.accompanist.swiperefresh.SwipeRefreshIndicator -import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.woocommerce.android.R import com.woocommerce.android.ui.compose.component.InfiniteListHandler import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.coupons.CouponListItem import com.woocommerce.android.ui.coupons.CouponListSkeleton +import com.woocommerce.android.ui.coupons.selector.LoadingState.Appending @Composable fun CouponSelectorScreen( @@ -112,6 +115,7 @@ fun EmptyCouponSelectorList( } } +@OptIn(ExperimentalMaterialApi::class) @Composable fun CouponSelectorList( coupons: List, @@ -120,17 +124,9 @@ fun CouponSelectorList( onRefresh: () -> Unit, onLoadMore: () -> Unit, ) { - SwipeRefresh( - state = rememberSwipeRefreshState(isRefreshing = loadingState == LoadingState.Refreshing), - onRefresh = onRefresh, - indicator = { state, refreshTrigger -> - SwipeRefreshIndicator( - state = state, - refreshTriggerDistance = refreshTrigger, - contentColor = MaterialTheme.colors.primary, - ) - } - ) { + val isRefreshing = loadingState == LoadingState.Refreshing + val pullRefreshState = rememberPullRefreshState(isRefreshing, { onRefresh() }) + Box(Modifier.pullRefresh(pullRefreshState)) { val listState = rememberLazyListState() LazyColumn( state = listState, @@ -145,7 +141,7 @@ fun CouponSelectorList( thickness = dimensionResource(id = R.dimen.minor_10) ) } - if (loadingState == LoadingState.Appending) { + if (loadingState == Appending) { item { CircularProgressIndicator( modifier = Modifier @@ -160,6 +156,13 @@ fun CouponSelectorList( InfiniteListHandler(listState = listState) { onLoadMore() } + + PullRefreshIndicator( + refreshing = isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + contentColor = MaterialTheme.colors.primary, + ) } } From 0d86515e0fe634ad1f467a884ed7a45322934b5c Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 17:58:08 +0100 Subject: [PATCH 088/119] Handle media upload and media fetch before creating blaze campaign --- .../android/media/MediaFilesRepository.kt | 21 ++++++++++++++ .../android/ui/blaze/BlazeRepository.kt | 29 +++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/media/MediaFilesRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/media/MediaFilesRepository.kt index 768fac5c962..fe6807a8aa2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/media/MediaFilesRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/media/MediaFilesRepository.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.media import android.content.Context import android.net.Uri +import com.woocommerce.android.OnChangedException import com.woocommerce.android.R import com.woocommerce.android.extensions.isNotNullOrEmpty import com.woocommerce.android.media.MediaFilesRepository.UploadResult.UploadFailure @@ -10,6 +11,7 @@ import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.util.CoroutineDispatchers import com.woocommerce.android.util.WooLog import com.woocommerce.android.util.WooLog.T +import com.woocommerce.android.util.dispatchAndAwait import com.woocommerce.android.viewmodel.ResourceProvider import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.channels.ProducerScope @@ -28,6 +30,8 @@ import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.generated.MediaActionBuilder import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.store.MediaStore +import org.wordpress.android.fluxc.store.MediaStore.MediaPayload +import org.wordpress.android.fluxc.store.MediaStore.OnMediaChanged import org.wordpress.android.fluxc.store.MediaStore.OnMediaUploaded import org.wordpress.android.mediapicker.MediaPickerUtils import org.wordpress.android.util.MediaUtils @@ -43,6 +47,23 @@ class MediaFilesRepository @Inject constructor( private val resourceProvider: ResourceProvider, private val mediaPickerUtils: MediaPickerUtils ) { + suspend fun fetchWordPressMedia(mediaId: Long): Result { + val result = dispatcher.dispatchAndAwait( + action = MediaActionBuilder.newFetchMediaAction( + MediaPayload( + selectedSite.get(), + MediaModel(selectedSite.get().localId().value, mediaId) + ) + ) + ) + + return if (result.isError) { + Result.failure(OnChangedException(result.error)) + } else { + Result.success(result.mediaList.first()) + } + } + suspend fun fetchMedia(localUri: String): MediaModel? { return withContext(dispatchers.io) { val mediaModel = FileUploadUtils.mediaModelFromLocalUri( 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 39d05828ba0..87eee869563 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 @@ -3,11 +3,14 @@ package com.woocommerce.android.ui.blaze import android.os.Parcelable import com.woocommerce.android.BuildConfig import com.woocommerce.android.OnChangedException +import com.woocommerce.android.media.MediaFilesRepository import com.woocommerce.android.model.CreditCardType import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.products.ProductDetailRepository import com.woocommerce.android.util.TimezoneProvider import com.woocommerce.android.util.WooLog +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.parcelize.Parcelize import org.wordpress.android.fluxc.model.MediaModel @@ -27,6 +30,7 @@ class BlazeRepository @Inject constructor( private val selectedSite: SelectedSite, private val blazeCampaignsStore: BlazeCampaignsStore, private val productDetailRepository: ProductDetailRepository, + private val mediaFilesRepository: MediaFilesRepository, private val timezoneProvider: TimezoneProvider, ) { companion object { @@ -219,7 +223,7 @@ class BlazeRepository @Inject constructor( campaignDetails: CampaignDetails, paymentMethodId: String ): Result { - val image = prepareCampaignImage().getOrElse { + val image = prepareCampaignImage(campaignDetails.campaignImage).getOrElse { return Result.failure(it) } @@ -260,8 +264,27 @@ class BlazeRepository @Inject constructor( } } - private fun prepareCampaignImage(): Result { - TODO() + private suspend fun prepareCampaignImage(image: BlazeCampaignImage): Result { + val result = when (image) { + is BlazeCampaignImage.LocalImage -> { + mediaFilesRepository.uploadFile(image.uri) + .filterNot { it is MediaFilesRepository.UploadResult.UploadProgress } + .first() + .let { + when (it) { + is MediaFilesRepository.UploadResult.UploadFailure -> Result.failure(it.error) + is MediaFilesRepository.UploadResult.UploadSuccess -> Result.success(it.media) + else -> error("Unexpected upload result: $it") + } + } + } + is BlazeCampaignImage.RemoteImage -> mediaFilesRepository.fetchWordPressMedia(image.mediaId) + is BlazeCampaignImage.None -> error("No image provided for Blaze Campaign Creation") + } + + return result.onFailure { + WooLog.w(WooLog.T.BLAZE, "Failed to prepare campaign image: ${it.message}") + } } @Parcelize From 6ede58664f88a047203390bb522767e42a16b209 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 17:58:35 +0100 Subject: [PATCH 089/119] Add a log message for campaign creation --- .../com/woocommerce/android/ui/blaze/BlazeRepository.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 87eee869563..7840c570495 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 @@ -260,7 +260,10 @@ class BlazeRepository @Inject constructor( Result.failure(OnChangedException(result.error)) } - else -> Result.success(Unit) + else -> { + WooLog.d(WooLog.T.BLAZE, "Campaign created successfully") + Result.success(Unit) + } } } From 2562325e93e4231ca47eb7c2e10246f4a79ed86f Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 17:59:16 +0100 Subject: [PATCH 090/119] Introduce a constant for blaze campaign creation --- .../kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 7840c570495..9374a06e1c9 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 @@ -34,6 +34,7 @@ class BlazeRepository @Inject constructor( private val timezoneProvider: TimezoneProvider, ) { companion object { + private const val BLAZE_CAMPAIGN_CREATION_ORIGIN = "wc-android" 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 = 5f // USD @@ -230,7 +231,7 @@ class BlazeRepository @Inject constructor( val result = blazeCampaignsStore.createCampaign( selectedSite.get(), request = BlazeCampaignCreationRequest( - origin = "wc-android", + origin = BLAZE_CAMPAIGN_CREATION_ORIGIN, originVersion = BuildConfig.VERSION_NAME, type = BlazeCampaignType.PRODUCT, paymentMethodId = paymentMethodId, From a3f0361419213baf9239bd395c8ad591a5efac36 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 17:59:19 +0100 Subject: [PATCH 091/119] Fix ktlint issue --- .../jetpack/main/JetpackActivationMainScreen.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt index cdf641ec382..72fdf72ec51 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/main/JetpackActivationMainScreen.kt @@ -109,8 +109,9 @@ fun JetpackActivationMainScreen( transition.AnimatedContent( contentKey = { it is JetpackActivationMainViewModel.ViewState.ErrorViewState }, transitionSpec = { - fadeIn(animationSpec = tween(DefaultDurationMillis, delayMillis = DefaultDurationMillis)) togetherWith - fadeOut(animationSpec = tween(DefaultDurationMillis)) + fadeIn( + animationSpec = tween(DefaultDurationMillis, delayMillis = DefaultDurationMillis) + ) togetherWith fadeOut(animationSpec = tween(DefaultDurationMillis)) }, modifier = Modifier.weight(1f) ) { targetState -> @@ -119,6 +120,7 @@ fun JetpackActivationMainScreen( viewState = targetState, onContinueClick = onContinueClick ) + is JetpackActivationMainViewModel.ViewState.ErrorViewState -> ErrorState( viewState = targetState, onGetHelpClick = onGetHelpClick, @@ -294,10 +296,13 @@ private fun AnimatedVisibilityScope.ErrorState( val retryButton = when (viewState.stepType) { JetpackActivationMainViewModel.StepType.Installation -> R.string.login_jetpack_installation_retry_installing + JetpackActivationMainViewModel.StepType.Activation -> R.string.login_jetpack_installation_retry_activating + JetpackActivationMainViewModel.StepType.Connection -> R.string.login_jetpack_installation_retry_authorizing + else -> null } retryButton?.let { @@ -328,9 +333,11 @@ private fun JetpackActivationStep( JetpackActivationMainViewModel.StepState.Idle -> { IdleCircle(indicatorModifier) } + JetpackActivationMainViewModel.StepState.Ongoing -> { CircularProgressIndicator(indicatorModifier) } + JetpackActivationMainViewModel.StepState.Success -> { Image( painter = painterResource(id = R.drawable.ic_progress_circle_complete), @@ -338,6 +345,7 @@ private fun JetpackActivationStep( modifier = indicatorModifier ) } + is JetpackActivationMainViewModel.StepState.Error -> { Icon( painter = painterResource(id = R.drawable.ic_gridicons_notice), @@ -385,6 +393,7 @@ private fun ConnectionStepHint(connectionStep: JetpackActivationMainViewModel.Co R.string.login_jetpack_steps_authorizing_validation, R.color.color_on_surface_medium ) + else -> Pair( R.string.login_jetpack_steps_authorizing_done, From 58015b41a5a1595e9fd1ea2ed136db144e53e968 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 18:09:23 +0100 Subject: [PATCH 092/119] Pass campaign details to the payment summary screen --- .../creation/payment/BlazeCampaignPaymentSummaryViewModel.kt | 2 +- .../creation/preview/BlazeCampaignCreationPreviewFragment.kt | 2 +- .../preview/BlazeCampaignCreationPreviewViewModel.kt | 5 ++--- .../res/navigation/nav_graph_blaze_campaign_creation.xml | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index 4a17fe69161..9bce1da179d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -34,7 +34,7 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( paymentMethodsState ) { selectedPaymentMethodId, paymentMethodState -> ViewState( - budget = navArgs.budget, + budget = navArgs.campaignDetails.budget, paymentMethodsState = paymentMethodState, selectedPaymentMethodId = selectedPaymentMethodId ) 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 814d45d1167..7f024e2392a 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 @@ -91,7 +91,7 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { is NavigateToPaymentSummary -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignPaymentSummaryFragment( - event.budget + event.campaignDetails ) ) } 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 cfd5f05f326..30a6e9d92f4 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 @@ -129,7 +129,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( fun onConfirmClicked() { campaignDetails.value?.let { - triggerEvent(NavigateToPaymentSummary(it.budget)) + triggerEvent(NavigateToPaymentSummary(it)) } } @@ -283,8 +283,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( val campaignImage: BlazeRepository.BlazeCampaignImage ) : MultiLiveEvent.Event() - // TODO we need to pass more details to use in the campaign creation data class NavigateToPaymentSummary( - val budget: BlazeRepository.Budget + val campaignDetails: BlazeRepository.CampaignDetails ) : MultiLiveEvent.Event() } 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 8d5744bbd7e..98f5451d237 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 @@ -142,8 +142,8 @@ android:name="com.woocommerce.android.ui.blaze.creation.payment.BlazeCampaignPaymentSummaryFragment" android:label="BlazeCampaignPaymentSummaryFragment"> + android:name="campaignDetails" + app:argType="com.woocommerce.android.ui.blaze.BlazeRepository$CampaignDetails" /> From 2f82031e7cd6c02c58b32a7a8578d17944d39a4c Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Tue, 13 Feb 2024 18:20:11 +0100 Subject: [PATCH 093/119] Remove unused Parcelization --- .../preview/BlazeCampaignCreationPreviewViewModel.kt | 6 +----- 1 file changed, 1 insertion(+), 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 30a6e9d92f4..ba3a02991cd 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 @@ -1,6 +1,5 @@ package com.woocommerce.android.ui.blaze.creation.preview -import android.os.Parcelable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope @@ -25,7 +24,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.parcelize.Parcelize import javax.inject.Inject @HiltViewModel @@ -232,11 +230,9 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( LOADED } - sealed interface AdDetailsUi : Parcelable { - @Parcelize + sealed interface AdDetailsUi { data object Loading : AdDetailsUi - @Parcelize data class AdDetails( val productId: Long, val description: String, From 2ff1039dae57fe748e4e2f1c6f939d5b50c9921b Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 13 Feb 2024 18:32:31 +0100 Subject: [PATCH 094/119] Move navigation details to nav_graph xml code --- .../payment/BlazeCampaignPaymentSummaryFragment.kt | 9 ++------- .../res/navigation/nav_graph_blaze_campaign_creation.xml | 4 +++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt index ae2a8215c60..82e4a0d5eb6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt @@ -6,8 +6,6 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController -import androidx.navigation.navOptions -import com.woocommerce.android.R import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.creation.payment.BlazeCampaignPaymentSummaryViewModel.NavigateToStartingScreenWithSuccessBottomSheet @@ -47,11 +45,8 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { private fun navigateBackToStartingScreen() { findNavController().navigateSafely( - directions = BlazeCampaignPaymentSummaryFragmentDirections - .actionBlazeCampaignPaymentSummaryFragmentToBlazeCampaignSuccessBottomSheetFragment(), - navOptions = navOptions { - popUpTo(R.id.nav_graph_blaze_campaign_creation) { inclusive = true } - } + BlazeCampaignPaymentSummaryFragmentDirections + .actionBlazeCampaignPaymentSummaryFragmentToBlazeCampaignSuccessBottomSheetFragment() ) } } 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 e65e01174a3..595dfdbe535 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 @@ -137,7 +137,9 @@ app:argType="com.woocommerce.android.ui.blaze.BlazeRepository$Budget" /> + app:destination="@id/blazeCampaignSuccessBottomSheetFragment" + app:popUpTo="@+id/nav_graph_blaze_campaign_creation" + app:popUpToInclusive="true" /> Date: Tue, 13 Feb 2024 19:00:40 +0100 Subject: [PATCH 095/119] Add a new support tag for Blaze campaign creation --- .../kotlin/com/woocommerce/android/support/help/HelpOrigin.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/support/help/HelpOrigin.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/support/help/HelpOrigin.kt index a8f169d6c40..a58d135c867 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/support/help/HelpOrigin.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/support/help/HelpOrigin.kt @@ -29,7 +29,8 @@ enum class HelpOrigin(private val stringValue: String) { DOMAIN_CHANGE("origin:domain-change"), UPGRADES("origin:upgrades"), ACCOUNT_DELETION("origin:account-deletion"), - ORDERS_LIST("origin:orders-list"); + ORDERS_LIST("origin:orders-list"), + BLAZE_CAMPAIGN_CREATION("origin:blaze-native-campaign-creation"); override fun toString(): String { return stringValue From 3f462ae1b394fec212786cc0fdc37c6bd631a0f0 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 19:01:06 +0100 Subject: [PATCH 096/119] Add a help toolbar button to the campaign payment summary --- .../BlazeCampaignPaymentSummaryFragment.kt | 2 ++ .../payment/BlazeCampaignPaymentSummaryScreen.kt | 16 +++++++++++----- .../BlazeCampaignPaymentSummaryViewModel.kt | 5 +++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt index 62c73453f07..5a148ade9be 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryFragment.kt @@ -8,6 +8,7 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateSafely +import com.woocommerce.android.extensions.navigateToHelpScreen import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.compose.composeView import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground @@ -39,6 +40,7 @@ class BlazeCampaignPaymentSummaryFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { MultiLiveEvent.Event.Exit -> findNavController().navigateUp() + is MultiLiveEvent.Event.NavigateToHelpScreen -> navigateToHelpScreen(event.origin) is BlazeCampaignPaymentSummaryViewModel.NavigateToPaymentsListScreen -> { findNavController().navigateSafely( BlazeCampaignPaymentSummaryFragmentDirections diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt index fb0ee04c129..5b989c6cef2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryScreen.kt @@ -36,7 +36,7 @@ import com.woocommerce.android.model.CreditCardType import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.compose.URL_ANNOTATION_TAG import com.woocommerce.android.ui.compose.annotatedStringRes -import com.woocommerce.android.ui.compose.component.Toolbar +import com.woocommerce.android.ui.compose.component.ToolbarWithHelpButton import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.util.ChromeCustomTabUtils @@ -47,7 +47,8 @@ fun BlazeCampaignPaymentSummaryScreen(viewModel: BlazeCampaignPaymentSummaryView viewModel.viewState.observeAsState().value?.let { BlazeCampaignPaymentSummaryScreen( state = it, - onBackClick = viewModel::onBackClicked + onBackClick = viewModel::onBackClicked, + onHelpClick = viewModel::onHelpClicked ) } } @@ -55,13 +56,17 @@ fun BlazeCampaignPaymentSummaryScreen(viewModel: BlazeCampaignPaymentSummaryView @Composable fun BlazeCampaignPaymentSummaryScreen( state: BlazeCampaignPaymentSummaryViewModel.ViewState, - onBackClick: () -> Unit + onBackClick: () -> Unit, + onHelpClick: () -> Unit ) { val context = LocalContext.current Scaffold( topBar = { - Toolbar(onNavigationButtonClick = onBackClick) + ToolbarWithHelpButton( + onNavigationButtonClick = onBackClick, + onHelpButtonClick = onHelpClick, + ) }, backgroundColor = MaterialTheme.colors.surface ) { paddingValues -> @@ -306,7 +311,8 @@ fun BlazeCampaignPaymentSummaryScreenPreview() { ), selectedPaymentMethodId = "1" ), - onBackClick = {} + onBackClick = {}, + onHelpClick = {} ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt index 4a17fe69161..277fec772ed 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/payment/BlazeCampaignPaymentSummaryViewModel.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.blaze.creation.payment import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope +import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.PaymentMethodsData import com.woocommerce.android.viewmodel.MultiLiveEvent @@ -48,6 +49,10 @@ class BlazeCampaignPaymentSummaryViewModel @Inject constructor( triggerEvent(MultiLiveEvent.Event.Exit) } + fun onHelpClicked() { + triggerEvent(MultiLiveEvent.Event.NavigateToHelpScreen(HelpOrigin.BLAZE_CAMPAIGN_CREATION)) + } + fun onPaymentMethodSelected(paymentMethodId: String) { selectedPaymentMethodId.value = paymentMethodId From 1d1bf8915e3d087012090d0a495681e397080690 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 13 Feb 2024 19:01:26 +0100 Subject: [PATCH 097/119] Add a help toolbar button to the campaign preview screen --- .../BlazeCampaignCreationPreviewFragment.kt | 5 +++++ .../preview/BlazeCampaignCreationPreviewScreen.kt | 14 +++++++++----- .../BlazeCampaignCreationPreviewViewModel.kt | 5 +++++ 3 files changed, 19 insertions(+), 5 deletions(-) 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 de79c3f177b..9bcb0b6c471 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 @@ -8,6 +8,7 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateSafely +import com.woocommerce.android.extensions.navigateToHelpScreen import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.BlazeRepository.Budget import com.woocommerce.android.ui.blaze.creation.ad.BlazeCampaignCreationEditAdFragment @@ -25,6 +26,7 @@ import com.woocommerce.android.ui.blaze.creation.targets.BlazeCampaignTargetSele 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 import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit import dagger.hilt.android.AndroidEntryPoint @@ -51,6 +53,9 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { is Exit -> findNavController().popBackStack() + + is MultiLiveEvent.Event.NavigateToHelpScreen -> navigateToHelpScreen(event.origin) + is NavigateToBudgetScreen -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignBudgetFragment(event.budget) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt index f71271b68f4..a82dbd36496 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/preview/BlazeCampaignCreationPreviewScreen.kt @@ -48,7 +48,7 @@ import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPr import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.CampaignDetailsUi import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewViewModel.CampaignPreviewUiState import com.woocommerce.android.ui.compose.animations.SkeletonView -import com.woocommerce.android.ui.compose.component.Toolbar +import com.woocommerce.android.ui.compose.component.ToolbarWithHelpButton import com.woocommerce.android.ui.compose.component.WCColoredButton import com.woocommerce.android.ui.compose.component.WCTextButton import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews @@ -60,7 +60,8 @@ fun BlazeCampaignCreationPreviewScreen(viewModel: BlazeCampaignCreationPreviewVi previewState = previewState, onBackPressed = viewModel::onBackPressed, onEditAdClicked = viewModel::onEditAdClicked, - onConfirmDetailsClicked = viewModel::onConfirmClicked + onConfirmDetailsClicked = viewModel::onConfirmClicked, + onHelpTapped = viewModel::onHelpTapped ) } } @@ -70,13 +71,15 @@ private fun BlazeCampaignCreationPreviewScreen( previewState: CampaignPreviewUiState, onBackPressed: () -> Unit, onEditAdClicked: () -> Unit, - onConfirmDetailsClicked: () -> Unit + onConfirmDetailsClicked: () -> Unit, + onHelpTapped: () -> Unit ) { Scaffold( topBar = { - Toolbar( + ToolbarWithHelpButton( title = stringResource(id = R.string.blaze_campaign_screen_fragment_title), onNavigationButtonClick = onBackPressed, + onHelpButtonClick = onHelpTapped, navigationIcon = Filled.ArrowBack ) }, @@ -413,7 +416,8 @@ fun CampaignScreenPreview() { ), onBackPressed = { }, onEditAdClicked = { }, - onConfirmDetailsClicked = { } + onConfirmDetailsClicked = { }, + onHelpTapped = { } ) } 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 e5bb642f0f5..ad41ee60ba1 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 @@ -7,6 +7,7 @@ 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.support.help.HelpOrigin import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeRepository.Budget import com.woocommerce.android.ui.blaze.BlazeRepository.CampaignPreview @@ -116,6 +117,10 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( triggerEvent(MultiLiveEvent.Event.Exit) } + fun onHelpTapped() { + triggerEvent(MultiLiveEvent.Event.NavigateToHelpScreen(HelpOrigin.BLAZE_CAMPAIGN_CREATION)) + } + fun onEditAdClicked() { (adDetails.value as? AdDetails)?.let { triggerEvent( From a993c3689c442f0aba54186c118c91f254bb3932 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 14 Feb 2024 08:16:39 +0100 Subject: [PATCH 098/119] Fix detekt issues --- .../creation/ad/BlazeCampaignCreationEditAdScreen.kt | 2 +- .../preview/BlazeCampaignCreationPreviewViewModel.kt | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt index c8af2a1f939..b1fabee6cb3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/ad/BlazeCampaignCreationEditAdScreen.kt @@ -366,7 +366,7 @@ fun PreviewCampaignEditAdContent() { WooThemeWithBackground { CampaignEditAdContent( viewState = ViewState( - adImage = BlazeCampaignImage.RemoteImage(0,"https://rb.gy/gmjuwb") + adImage = BlazeCampaignImage.RemoteImage(0, "https://rb.gy/gmjuwb") ), onTagLineChanged = { }, onDescriptionChanged = { }, 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 ba3a02991cd..f6c35f1caca 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 @@ -167,9 +167,12 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( displayValue = targetingParameters.languages.joinToString { it.name } .ifEmpty { resourceProvider.getString(string.blaze_campaign_preview_target_default_value) }, onItemSelected = { - triggerEvent(NavigateToTargetSelectionScreen( - targetType = LANGUAGE, - selectedIds = targetingParameters.languages.map { it.code })) + triggerEvent( + NavigateToTargetSelectionScreen( + targetType = LANGUAGE, + selectedIds = targetingParameters.languages.map { it.code } + ) + ) }, ), CampaignDetailItemUi( From 900d0c43beed10775c097f53efd02665570451dc Mon Sep 17 00:00:00 2001 From: Spencer Transier Date: Wed, 14 Feb 2024 03:34:17 -0800 Subject: [PATCH 099/119] Use 17.3 changes --- WooCommerce/build.gradle | 3 ++- settings.gradle | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 1dbba01f9ab..3dffa9f78c5 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -280,7 +280,8 @@ dependencies { implementation "com.github.bumptech.glide:glide:$glideVersion" kapt "com.github.bumptech.glide:compiler:$glideVersion" implementation "com.github.bumptech.glide:volley-integration:$glideVersion@aar" - implementation "com.google.android.play:core:$googlePlayCoreVersion" + implementation 'com.google.android.play:app-update-ktx:2.1.0' + implementation 'com.google.android.play:review-ktx:2.0.1' implementation 'com.google.android.gms:play-services-code-scanner:16.1.0' diff --git a/settings.gradle b/settings.gradle index 1912ba4f54f..cf0dbbdfb44 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,7 +5,7 @@ pluginManagement { gradle.ext.detektVersion = '1.19.0' gradle.ext.kotlinVersion = '1.9.22' gradle.ext.measureBuildsVersion = '2.0.3' - gradle.ext.navigationVersion = '2.5.3' + gradle.ext.navigationVersion = '2.7.7' gradle.ext.sentryVersion = '3.5.0' gradle.ext.violationCommentsVersion = '1.69.0' From fc8a592baf3e6b7ee3a88390b3496bcda5843005 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 6 Feb 2024 19:14:17 +0100 Subject: [PATCH 100/119] Upgrade Dangermattic Gem --- Gemfile | 2 +- Gemfile.lock | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 9a8fd99671d..adb0be37dfa 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'danger-dangermattic', git: 'https://github.com/Automattic/dangermattic' +gem 'danger-dangermattic', git: 'https://github.com/Automattic/dangermattic', ref: 'iangmaia/common-utils' gem 'fastlane', '~> 2.216' gem 'nokogiri' gem 'rubocop', '~> 1.56' diff --git a/Gemfile.lock b/Gemfile.lock index 3d8a1823ad4..95d68fb21ca 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,7 @@ GIT remote: https://github.com/Automattic/dangermattic - revision: 06a54db4f546d20c0465e4d144049d061a2a1e20 + revision: a05023fa2b877c0d778c14bfff891c1318dd9f63 + ref: iangmaia/common-utils specs: danger-dangermattic (0.0.1) danger (~> 9.3) From cddf070cb0ebce58b47525fffb42228c0d39775e Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 6 Feb 2024 19:14:27 +0100 Subject: [PATCH 101/119] Update Dangerfile to use latest Dangermattic plugins --- Dangerfile | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/Dangerfile b/Dangerfile index cfa6cfacb71..155a45f9b2f 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,25 +1,25 @@ # frozen_string_literal: true -def release_branch? - danger.github.branch_for_base.start_with?('release/') || danger.github.branch_for_base.start_with?('hotfix/') -end +github.dismiss_out_of_range_messages -def main_branch? - danger.github.branch_for_base == 'trunk' -end +# `files: []` forces rubocop to scan all files, not just the ones modified in the PR +rubocop.lint(files: [], force_exclusion: true, inline_comment: true, fail_on_inline_comment: true, include_cop_names: true) -def wip_feature? - has_wip_label = github.pr_labels.any? { |label| label.include?('WIP') } - has_wip_title = github.pr_title.include?('WIP') +manifest_pr_checker.check_gemfile_lock_updated - has_wip_label || has_wip_title -end +android_release_checker.check_release_notes_and_play_store_strings -return if github.pr_labels.include?('Releases') +android_strings_checker.check_strings_do_not_refer_resource -github.dismiss_out_of_range_messages +# skip remaining checks if we're during the release process +if github.pr_labels.include?('Releases') + message('This PR has the `Releases` label: some checks will be skipped.') + return +end -manifest_pr_checker.check_gemfile_lock_updated +common_release_checker.check_internal_release_notes_changed(report_type: :message) + +android_release_checker.check_modified_strings_on_release labels_checker.check( do_not_merge_labels: ['status: do not merge'], @@ -27,16 +27,28 @@ labels_checker.check( required_labels_error: 'PR requires at least one label.' ) -view_changes_need_screenshots.view_changes_need_screenshots +tracks_checker.check_tracks_changes( + tracks_files: [ + 'AnalyticsTracker.kt', + 'AnalyticsEvent.kt', + 'LoginAnalyticsTracker.kt' + ], + tracks_usage_matchers: [ + /AnalyticsTracker\.track/ + ], + tracks_label: 'Tracks' +) + +view_changes_checker.check pr_size_checker.check_diff_size( - file_selector: ->(path) { !path.include?('/src/test') }, - max_size: 300 + max_size: 300, + file_selector: ->(path) { !path.include?('/src/test') } ) android_unit_test_checker.check_missing_tests # skip check for draft PRs and for WIP features unless the PR is against the main branch or release branch -milestone_checker.check_milestone_due_date(days_before_due: 2) unless github.pr_draft? || (wip_feature? && !(release_branch? || main_branch?)) - -rubocop.lint(inline_comment: true, fail_on_inline_comment: true, include_cop_names: true) +unless github.pr_draft? || (github_utils.wip_feature? && !(github_utils.release_branch? || github_utils.main_branch?)) + milestone_checker.check_milestone_due_date(days_before_due: 2) +end From 303dc78f8ee0d8f75fc854debd3be0fa7fde53d2 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Thu, 8 Feb 2024 14:15:49 +0100 Subject: [PATCH 102/119] Use published Dangermattic Gem --- Gemfile | 4 ++-- Gemfile.lock | 58 +++++++++++++++++++++++----------------------------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/Gemfile b/Gemfile index adb0be37dfa..4bdca7c0fc5 100644 --- a/Gemfile +++ b/Gemfile @@ -2,10 +2,10 @@ source 'https://rubygems.org' -gem 'danger-dangermattic', git: 'https://github.com/Automattic/dangermattic', ref: 'iangmaia/common-utils' +gem 'danger-dangermattic', '~> 1.0' gem 'fastlane', '~> 2.216' gem 'nokogiri' -gem 'rubocop', '~> 1.56' +gem 'rubocop', '~> 1.60' ### Fastlane Plugins diff --git a/Gemfile.lock b/Gemfile.lock index 95d68fb21ca..6c300467ded 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,17 +1,3 @@ -GIT - remote: https://github.com/Automattic/dangermattic - revision: a05023fa2b877c0d778c14bfff891c1318dd9f63 - ref: iangmaia/common-utils - specs: - danger-dangermattic (0.0.1) - danger (~> 9.3) - danger-junit (~> 1.0) - danger-plugin-api (~> 1.0) - danger-rubocop (~> 0.11) - danger-swiftlint (~> 0.29) - danger-xcode_summary (~> 1.0) - rubocop (~> 1.56) - GEM remote: https://rubygems.org/ specs: @@ -27,7 +13,7 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) ast (2.4.2) @@ -67,7 +53,7 @@ GEM connection_pool (2.4.1) cork (0.3.0) colored2 (~> 3.1) - danger (9.3.2) + danger (9.4.3) claide (~> 1.0) claide-plugins (>= 0.9.2) colored2 (~> 3.1) @@ -78,8 +64,16 @@ GEM kramdown (~> 2.3) kramdown-parser-gfm (~> 1.0) no_proxy_fix - octokit (~> 6.0) + octokit (>= 4.0) terminal-table (>= 1, < 4) + danger-dangermattic (1.0.0) + danger (~> 9.4) + danger-junit (~> 1.0) + danger-plugin-api (~> 1.0) + danger-rubocop (~> 0.12) + danger-swiftlint (~> 0.35) + danger-xcode_summary (~> 1.0) + rubocop (~> 1.60) danger-junit (1.0.2) danger (> 2.0) ox (~> 2.0) @@ -88,10 +82,10 @@ GEM danger-rubocop (0.12.0) danger rubocop (~> 1.0) - danger-swiftlint (0.33.0) + danger-swiftlint (0.35.0) danger rake (> 10) - thor (~> 0.19) + thor (~> 1.0.0) danger-xcode_summary (1.2.0) danger-plugin-api (~> 1.0) xcresult (~> 0.2) @@ -123,7 +117,7 @@ GEM faraday-em_http (1.0.0) faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) - faraday-http-cache (2.5.0) + faraday-http-cache (2.5.1) faraday (>= 0.8) faraday-httpclient (1.0.1) faraday-multipart (1.0.4) @@ -194,7 +188,7 @@ GEM rake-compiler (~> 1.0) xcodeproj (~> 1.22) gh_inspector (1.1.3) - git (1.18.0) + git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) google-apis-androidpublisher_v3 (0.53.0) @@ -242,7 +236,7 @@ GEM concurrent-ruby (~> 1.0) java-properties (0.3.0) jmespath (1.6.2) - json (2.6.3) + json (2.7.1) jwt (2.7.1) kramdown (2.4.0) rexml @@ -254,7 +248,7 @@ GEM mini_portile2 (2.8.5) minitest (5.20.0) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) mutex_m (0.1.2) nanaimo (0.3.0) nap (1.1.0) @@ -273,8 +267,8 @@ GEM optparse (0.1.1) os (1.1.4) ox (2.14.17) - parallel (1.23.0) - parser (3.2.2.4) + parallel (1.24.0) + parser (3.3.0.5) ast (~> 2.4.1) racc plist (3.7.0) @@ -288,7 +282,7 @@ GEM rake-compiler (1.2.5) rake rchardet (1.8.0) - regexp_parser (2.8.2) + regexp_parser (2.9.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -297,15 +291,15 @@ GEM rexml (3.2.6) rmagick (4.3.0) rouge (2.0.7) - rubocop (1.57.2) + rubocop (1.60.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.4) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.30.0) @@ -328,7 +322,7 @@ GEM terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - thor (0.20.3) + thor (1.0.1) trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) @@ -358,12 +352,12 @@ PLATFORMS ruby DEPENDENCIES - danger-dangermattic! + danger-dangermattic (~> 1.0) fastlane (~> 2.216) fastlane-plugin-wpmreleasetoolkit (~> 9.2) nokogiri rmagick (~> 4.1) - rubocop (~> 1.56) + rubocop (~> 1.60) BUNDLED WITH 2.4.19 From b7aa376e4da8d388068c0463c1a34ca925879d08 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Thu, 8 Feb 2024 18:58:06 +0100 Subject: [PATCH 103/119] Use versioned shared GitHub Actions Workflow --- .github/workflows/run-danger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-danger.yml b/.github/workflows/run-danger.yml index d61c242186c..910ff3bc717 100644 --- a/.github/workflows/run-danger.yml +++ b/.github/workflows/run-danger.yml @@ -6,6 +6,6 @@ on: jobs: dangermattic: - uses: Automattic/dangermattic/.github/workflows/reusable-run-danger.yml@trunk + uses: Automattic/dangermattic/.github/workflows/reusable-run-danger.yml@v1.0.0 secrets: github-token: ${{ secrets.DANGERMATTIC_GITHUB_TOKEN }} From 65274d5273e07ac60791131cabcccc8f9211d4b9 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 12 Feb 2024 19:30:20 +0100 Subject: [PATCH 104/119] Tweaks to prevent unnecessary checks running while a PR is a Draft --- .github/workflows/run-danger.yml | 4 +++- Dangerfile | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/run-danger.yml b/.github/workflows/run-danger.yml index 910ff3bc717..856ab8cea46 100644 --- a/.github/workflows/run-danger.yml +++ b/.github/workflows/run-danger.yml @@ -2,10 +2,12 @@ name: ☢️ Danger on: pull_request: - types: [opened, synchronize, edited, review_requested, review_request_removed, labeled, unlabeled, milestoned, demilestoned] + types: [opened, reopened, ready_for_review, synchronize, edited, labeled, unlabeled, milestoned, demilestoned] jobs: dangermattic: + # runs on draft PRs only for opened / synchronize events + if: ${{ (github.event.pull_request.draft == false) || (github.event.pull_request.draft == true && contains(fromJSON('["opened", "synchronize"]'), github.event.action)) }} uses: Automattic/dangermattic/.github/workflows/reusable-run-danger.yml@v1.0.0 secrets: github-token: ${{ secrets.DANGERMATTIC_GITHUB_TOKEN }} diff --git a/Dangerfile b/Dangerfile index 155a45f9b2f..9e5b8f1c0a4 100644 --- a/Dangerfile +++ b/Dangerfile @@ -21,12 +21,6 @@ common_release_checker.check_internal_release_notes_changed(report_type: :messag android_release_checker.check_modified_strings_on_release -labels_checker.check( - do_not_merge_labels: ['status: do not merge'], - required_labels: [//], - required_labels_error: 'PR requires at least one label.' -) - tracks_checker.check_tracks_changes( tracks_files: [ 'AnalyticsTracker.kt', @@ -48,7 +42,14 @@ pr_size_checker.check_diff_size( android_unit_test_checker.check_missing_tests +# skip remaining checks if we have a Draft PR +return if github.pr_draft? + +labels_checker.check( + do_not_merge_labels: ['status: do not merge'], + required_labels: [//], + required_labels_error: 'PR requires at least one label.' +) + # skip check for draft PRs and for WIP features unless the PR is against the main branch or release branch -unless github.pr_draft? || (github_utils.wip_feature? && !(github_utils.release_branch? || github_utils.main_branch?)) - milestone_checker.check_milestone_due_date(days_before_due: 2) -end +milestone_checker.check_milestone_due_date(days_before_due: 2) unless github_utils.wip_feature? && !(github_utils.release_branch? || github_utils.main_branch?) From 6b02b688b210c0bfabe0103bb3ca3f2fd618180e Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Tue, 13 Feb 2024 17:46:53 +0100 Subject: [PATCH 105/119] Update Dangerfile after PR feedback --- Dangerfile | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Dangerfile b/Dangerfile index 9e5b8f1c0a4..6c05a7fb8e3 100644 --- a/Dangerfile +++ b/Dangerfile @@ -11,7 +11,7 @@ android_release_checker.check_release_notes_and_play_store_strings android_strings_checker.check_strings_do_not_refer_resource -# skip remaining checks if we're during the release process +# skip remaining checks if we're in a release-process PR if github.pr_labels.include?('Releases') message('This PR has the `Releases` label: some checks will be skipped.') return @@ -42,8 +42,11 @@ pr_size_checker.check_diff_size( android_unit_test_checker.check_missing_tests -# skip remaining checks if we have a Draft PR -return if github.pr_draft? +# skip remaining checks if the PR is still a Draft +if github.pr_draft? + message('This PR is still a Draft: some checks will be skipped.') + return +end labels_checker.check( do_not_merge_labels: ['status: do not merge'], @@ -51,5 +54,5 @@ labels_checker.check( required_labels_error: 'PR requires at least one label.' ) -# skip check for draft PRs and for WIP features unless the PR is against the main branch or release branch -milestone_checker.check_milestone_due_date(days_before_due: 2) unless github_utils.wip_feature? && !(github_utils.release_branch? || github_utils.main_branch?) +# runs the milestone check if this is not a WIP feature and the PR is against the main branch or the release branch +milestone_checker.check_milestone_due_date(days_before_due: 2) if (github_utils.main_branch? || github_utils.release_branch?) && !github_utils.wip_feature? From e70ba8029e4948ab32575467ec6660a8441afcc6 Mon Sep 17 00:00:00 2001 From: Automattic Release Bot Date: Wed, 14 Feb 2024 13:30:15 +0000 Subject: [PATCH 106/119] Bump version number --- version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.properties b/version.properties index b50169bf8df..5bcf47aba63 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -versionName=17.3-rc-1 -versionCode=507 +versionName=17.3-rc-2 +versionCode=508 \ No newline at end of file From 63deb8b5f65a0e8e606244d0c7475c059eb423b3 Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 14 Feb 2024 07:51:03 -0600 Subject: [PATCH 107/119] add cog wheel icon to manage analytic cards --- .../ui/analytics/hub/AnalyticsHubFragment.kt | 32 +++++++++++++++++++ .../main/res/drawable/ic_configuration.xml | 4 +-- .../main/res/menu/menu_analytics_settings.xml | 10 ++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 WooCommerce/src/main/res/menu/menu_analytics_settings.xml diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/AnalyticsHubFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/AnalyticsHubFragment.kt index 6f4093563e3..203ca23e248 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/AnalyticsHubFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/AnalyticsHubFragment.kt @@ -1,9 +1,14 @@ package com.woocommerce.android.ui.analytics.hub import android.os.Bundle +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View +import androidx.core.view.MenuProvider import androidx.core.view.isVisible import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -21,6 +26,7 @@ import com.woocommerce.android.ui.analytics.ranges.StatsTimeRangeSelection.Selec import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.feedback.SurveyType import com.woocommerce.android.util.ChromeCustomTabUtils +import com.woocommerce.android.util.FeatureFlag import com.woocommerce.android.viewmodel.MultiLiveEvent import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn @@ -51,6 +57,9 @@ class AnalyticsHubFragment : BaseFragment(R.layout.fragment_analytics) { super.onViewCreated(view, savedInstanceState) bind(view) setupResultHandlers(viewModel) + if (FeatureFlag.EXPANDED_ANALYTIC_HUB_M2.isEnabled()) { + setupMenu() + } viewLifecycleOwner.lifecycleScope.launch { viewModel.viewState.flowWithLifecycle(lifecycle).collect { newState -> handleStateChange(newState) } @@ -77,6 +86,7 @@ class AnalyticsHubFragment : BaseFragment(R.layout.fragment_analytics) { is AnalyticsViewEvent.OpenUrl -> ChromeCustomTabUtils.launchUrl(requireContext(), event.url) is AnalyticsViewEvent.OpenWPComWebView -> findNavController() .navigate(NavGraphMainDirections.actionGlobalWPComWebViewFragment(urlToLoad = event.url)) + is AnalyticsViewEvent.OpenDatePicker -> showDateRangePicker(event.fromMillis, event.toMillis) is AnalyticsViewEvent.OpenDateRangeSelector -> openDateRangeSelector() is AnalyticsViewEvent.SendFeedback -> sendFeedback() @@ -164,4 +174,26 @@ class AnalyticsHubFragment : BaseFragment(R.layout.fragment_analytics) { .actionGlobalFeedbackSurveyFragment(SurveyType.ANALYTICS_HUB) .apply { findNavController().navigateSafely(this) } } + + private fun setupMenu() { + requireActivity().addMenuProvider( + object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.menu_analytics_settings, menu) + } + + override fun onMenuItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.menu_settings) { + findNavController() + .navigateSafely(AnalyticsHubFragmentDirections.actionAnalyticsToAnalyticsSettings()) + return true + } + + return false + } + }, + viewLifecycleOwner, + Lifecycle.State.RESUMED + ) + } } diff --git a/WooCommerce/src/main/res/drawable/ic_configuration.xml b/WooCommerce/src/main/res/drawable/ic_configuration.xml index 438f7a10c8c..f32d5b0798f 100644 --- a/WooCommerce/src/main/res/drawable/ic_configuration.xml +++ b/WooCommerce/src/main/res/drawable/ic_configuration.xml @@ -1,6 +1,6 @@ - + android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/WooCommerce/src/main/res/menu/menu_analytics_settings.xml b/WooCommerce/src/main/res/menu/menu_analytics_settings.xml new file mode 100644 index 00000000000..ee695f4c382 --- /dev/null +++ b/WooCommerce/src/main/res/menu/menu_analytics_settings.xml @@ -0,0 +1,10 @@ + + + + From 82754be0eba607411a9df54e8ad888e12c2a3efb Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 14 Feb 2024 07:53:01 -0600 Subject: [PATCH 108/119] navigate to manage analytic screen --- WooCommerce/src/main/res/navigation/nav_graph_main.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WooCommerce/src/main/res/navigation/nav_graph_main.xml b/WooCommerce/src/main/res/navigation/nav_graph_main.xml index f9daa8b705a..124b55ee757 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_main.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_main.xml @@ -221,7 +221,15 @@ + + Date: Wed, 14 Feb 2024 07:53:25 -0600 Subject: [PATCH 109/119] manage analytics ui --- .../settings/AnalyticsHubSettingFragment.kt | 47 +++++ .../hub/settings/AnalyticsHubSettingScreen.kt | 190 ++++++++++++++++++ .../settings/AnalyticsHubSettingsViewModel.kt | 57 ++++++ WooCommerce/src/main/res/values/ids.xml | 1 + WooCommerce/src/main/res/values/strings.xml | 3 + 5 files changed, 298 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingFragment.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingsViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingFragment.kt new file mode 100644 index 00000000000..d24e9d32e75 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingFragment.kt @@ -0,0 +1,47 @@ +package com.woocommerce.android.ui.analytics.hub.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.woocommerce.android.R +import com.woocommerce.android.ui.base.BaseFragment +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import com.woocommerce.android.ui.main.AppBarStatus +import com.woocommerce.android.viewmodel.MultiLiveEvent + +class AnalyticsHubSettingFragment : BaseFragment() { + + private val viewModel: AnalyticsHubSettingsViewModel by viewModels() + + override val activityAppBarStatus: AppBarStatus = AppBarStatus.Hidden + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return ComposeView(requireContext()).apply { + id = R.id.analytics_hub_settings_view + + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + + setContent { + WooThemeWithBackground { + AnalyticsHubSettingScreen(viewModel) + } + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.event.observe(viewLifecycleOwner) { event -> handleEvent(event) } + } + + private fun handleEvent(event: MultiLiveEvent.Event) { + when (event) { + is MultiLiveEvent.Event.Exit -> findNavController().popBackStack() + } + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt new file mode 100644 index 00000000000..4594d59f5e5 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt @@ -0,0 +1,190 @@ +package com.woocommerce.android.ui.analytics.hub.settings + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.DragHandle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.woocommerce.android.R +import com.woocommerce.android.ui.compose.component.AlertDialog +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import com.woocommerce.android.ui.orders.creation.configuration.SelectionCheck + +@Composable +fun AnalyticsHubSettingScreen(viewModel: AnalyticsHubSettingsViewModel) { + BackHandler(onBack = viewModel::onBackPressed) + Scaffold(topBar = { + TopAppBar( + title = { Text(text = stringResource(id = R.string.manage_analytics)) }, + navigationIcon = { + IconButton(viewModel::onBackPressed) { + Icon( + Icons.Filled.Close, + contentDescription = stringResource(id = R.string.back) + ) + } + }, + backgroundColor = colorResource(id = R.color.color_toolbar), + actions = { + TextButton(viewModel::onSaveChanges) { + Text( + text = stringResource(id = R.string.save).uppercase() + ) + } + }, + ) + }) { padding -> + viewModel.viewStateData.liveData.observeAsState().value?.let { state -> + AnalyticsHubSettingScreen( + cards = state.cards, + onSelectionChange = viewModel::onSelectionChange, + modifier = Modifier.padding(padding) + ) + + if (state.showDismissDialog) { + DiscardChangesDialog( + dismissButton = viewModel::onDismissDiscardChanges, + discardButton = viewModel::onDiscardChanges + ) + } + } + } +} + +@Composable +fun AnalyticsHubSettingScreen( + cards: List, + onSelectionChange: (Long, Boolean) -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colors.surface) + ) { + Text( + modifier = Modifier.padding(start = 16.dp, top = 24.dp), + text = stringResource(id = R.string.analytic_cards).uppercase(), + style = MaterialTheme.typography.caption, + fontWeight = FontWeight.Bold + ) + + LazyColumn(modifier = Modifier.padding(top = 16.dp)) { + itemsIndexed(cards) { i, card -> + AnalyticCardItem( + showTopDivider = i == 0, + id = card.id, + title = card.title, + isSelected = card.isVisible, + onSelectionChange = onSelectionChange + ) + } + } + } +} + +@Composable +fun AnalyticCardItem( + id: Long, + title: String, + isSelected: Boolean, + onSelectionChange: (Long, Boolean) -> Unit, + modifier: Modifier = Modifier, + showTopDivider: Boolean = false +) { + Column { + if (showTopDivider) Divider() + Row( + modifier = modifier + .clickable { onSelectionChange(id, !isSelected) } + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + SelectionCheck( + isSelected = isSelected, + onSelectionChange = { onSelectionChange(id, !isSelected) } + ) + Text( + text = title.uppercase(), + modifier + .weight(2f) + .padding(horizontal = 16.dp) + ) + Icon( + imageVector = Icons.Filled.DragHandle, + contentDescription = stringResource(id = R.string.drag_handle) + ) + } + Divider() + } +} + +@Composable +fun DiscardChangesDialog( + discardButton: () -> Unit, + dismissButton: () -> Unit +) { + AlertDialog( + onDismissRequest = {}, + text = { + Text(text = stringResource(id = R.string.discard_message)) + }, + confirmButton = { + TextButton(onClick = dismissButton) { + Text(stringResource(id = R.string.keep_editing).uppercase()) + } + }, + dismissButton = { + TextButton(onClick = discardButton) { + Text(stringResource(id = R.string.discard).uppercase()) + } + }, + neutralButton = {} + ) +} + +@Composable +@Preview(name = "Screen", device = Devices.PIXEL_4) +fun AnalyticsHubSettingScreenPreview() { + AnalyticsHubSettingScreen( + listOf( + AnalyticCardConfiguration(1L, "Revenue", true), + AnalyticCardConfiguration(2L, "Orders", true), + AnalyticCardConfiguration(3L, "Stats", false) + ), + onSelectionChange = { _, _ -> } + ) +} + +@Composable +@Preview +fun AnalyticCardItemPreview() { + WooThemeWithBackground { + AnalyticCardItem(id = 1L, title = "Revenue", isSelected = true, onSelectionChange = { _, _ -> }) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingsViewModel.kt new file mode 100644 index 00000000000..87e8e6f7b23 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingsViewModel.kt @@ -0,0 +1,57 @@ +package com.woocommerce.android.ui.analytics.hub.settings + +import android.os.Parcelable +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.viewmodel.LiveDataDelegate +import com.woocommerce.android.viewmodel.MultiLiveEvent +import com.woocommerce.android.viewmodel.ScopedViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.parcelize.Parcelize +import javax.inject.Inject + +@HiltViewModel +class AnalyticsHubSettingsViewModel @Inject constructor(savedState: SavedStateHandle) : ScopedViewModel(savedState) { + val viewStateData = LiveDataDelegate(savedState, AnalyticsHubSettingsViewState()) + private var viewState by viewStateData + fun onBackPressed() { + viewState = viewState.copy(showDismissDialog = true) + } + + fun onDismissDiscardChanges() { + viewState = viewState.copy(showDismissDialog = false) + } + + fun onDiscardChanges() { + triggerEvent(MultiLiveEvent.Event.Exit) + } + + fun onSaveChanges() { + triggerEvent(MultiLiveEvent.Event.Exit) + } + + fun onSelectionChange(id: Long, isSelected: Boolean) { + val updatedList = viewState.cards.map { card -> + if (card.id == id) card.copy(isVisible = isSelected) + else card + } + viewState = viewState.copy(cards = updatedList) + } +} + +@Suppress("MagicNumber") +@Parcelize +data class AnalyticsHubSettingsViewState( + val cards: List = listOf( + AnalyticCardConfiguration(1L, "Revenue", true), + AnalyticCardConfiguration(2L, "Orders", true), + AnalyticCardConfiguration(3L, "Stats", false) + ), + val showDismissDialog: Boolean = false +) : Parcelable + +@Parcelize +data class AnalyticCardConfiguration( + val id: Long, + val title: String, + val isVisible: Boolean +) : Parcelable diff --git a/WooCommerce/src/main/res/values/ids.xml b/WooCommerce/src/main/res/values/ids.xml index 8f0fc93c3a1..6e29705c2cd 100644 --- a/WooCommerce/src/main/res/values/ids.xml +++ b/WooCommerce/src/main/res/values/ids.xml @@ -4,4 +4,5 @@ + diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index cdadf049679..dd52fcabde8 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -138,6 +138,9 @@ %s Required field Analytics + Manage Analytics + Analytic Cards + Drag handle Card Cash Create From 0ba3917fb74e3945328fff621da34a77f5f3d01a Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 14 Feb 2024 16:12:24 +0100 Subject: [PATCH 110/119] Add existing tracking event to Blaze intro --- .../campaigs/BlazeCampaignListFragment.kt | 2 +- .../BlazeCampaignCreationDispatcher.kt | 33 +++++++++++++------ .../BlazeCampaignCreationIntroViewModel.kt | 13 +++++++- .../android/ui/moremenu/MoreMenuFragment.kt | 3 +- .../android/ui/mystore/MyStoreFragment.kt | 5 ++- .../ui/products/ProductDetailFragment.kt | 6 +++- .../nav_graph_blaze_campaign_creation.xml | 4 +++ 7 files changed, 51 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt index 8a65f50080d..bb28dd2135c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt @@ -69,7 +69,7 @@ class BlazeCampaignListFragment : BaseFragment() { private fun openBlazeCreationFlow() { lifecycleScope.launch { - blazeCampaignCreationDispatcher.startCampaignCreation() + blazeCampaignCreationDispatcher.startCampaignCreation(source = BlazeFlowSource.CAMPAIGN_LIST) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt index 2a97795e66d..9580510c71d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt @@ -10,6 +10,7 @@ import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment import com.woocommerce.android.ui.blaze.BlazeRepository +import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.blaze.creation.intro.BlazeCampaignCreationIntroFragmentArgs import com.woocommerce.android.ui.blaze.creation.preview.BlazeCampaignCreationPreviewFragmentArgs import com.woocommerce.android.ui.products.ProductListRepository @@ -43,31 +44,38 @@ class BlazeCampaignCreationDispatcher @Inject constructor( } suspend fun startCampaignCreation( + source: BlazeFlowSource, productId: Long? = null, handler: (BlazeCampaignCreationDispatcherEvent) -> Unit = ::handleEvent ) { when { blazeRepository.getMostRecentCampaign() == null -> handler( - BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationIntro(productId) + BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationIntro(productId, source) ) - else -> startCampaignCreationWithoutIntro(productId, handler) + else -> startCampaignCreationWithoutIntro(productId, source, handler) } } private suspend fun startCampaignCreationWithoutIntro( productId: Long?, + source: BlazeFlowSource, handler: (BlazeCampaignCreationDispatcherEvent) -> Unit ) { val products = getPublishedCachedProducts() when { productId != null -> { - handler(BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm(productId)) + handler(BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm(productId, source)) } products.size == 1 -> { - handler(BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm(products.first().remoteId)) + handler( + BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm( + products.first().remoteId, + source + ) + ) } products.isNotEmpty() -> { @@ -92,7 +100,7 @@ class BlazeCampaignCreationDispatcher @Inject constructor( private fun handleEvent(event: BlazeCampaignCreationDispatcherEvent) { when (event) { is BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationIntro -> fragmentReference.get() - ?.showIntro(event.productId) + ?.showIntro(event.productId, event.blazeSource) is BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm -> fragmentReference.get() ?.showCampaignPreview(event.productId) @@ -102,10 +110,13 @@ class BlazeCampaignCreationDispatcher @Inject constructor( } } - private fun BaseFragment.showIntro(productId: Long?) { + private fun BaseFragment.showIntro(productId: Long?, blazeSource: BlazeFlowSource) { findNavController().navigateToBlazeGraph( startDestination = R.id.blazeCampaignCreationIntroFragment, - bundle = BlazeCampaignCreationIntroFragmentArgs(productId ?: -1L).toBundle() + bundle = BlazeCampaignCreationIntroFragmentArgs( + productId = productId ?: -1L, + source = blazeSource + ).toBundle() ) } @@ -155,13 +166,15 @@ class BlazeCampaignCreationDispatcher @Inject constructor( sealed interface BlazeCampaignCreationDispatcherEvent { data class ShowBlazeCampaignCreationIntro( - val productId: Long? + val productId: Long?, + val blazeSource: BlazeFlowSource ) : BlazeCampaignCreationDispatcherEvent data class ShowBlazeCampaignCreationForm( - val productId: Long + val productId: Long, + val blazeSource: BlazeFlowSource ) : BlazeCampaignCreationDispatcherEvent - object ShowProductSelectorScreen : BlazeCampaignCreationDispatcherEvent + data object ShowProductSelectorScreen : BlazeCampaignCreationDispatcherEvent } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt index e05bc900361..0dedbf320ae 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt @@ -1,6 +1,9 @@ package com.woocommerce.android.ui.blaze.creation.intro import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_INTRO_DISPLAYED +import com.woocommerce.android.analytics.AnalyticsTracker +import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.ui.products.ProductListRepository import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.util.CoroutineDispatchers @@ -20,7 +23,8 @@ import javax.inject.Inject class BlazeCampaignCreationIntroViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val productListRepository: ProductListRepository, - private val coroutineDispatchers: CoroutineDispatchers + private val coroutineDispatchers: CoroutineDispatchers, + private val analyticsTracker: AnalyticsTrackerWrapper, ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationIntroFragmentArgs by savedStateHandle.navArgs() fun onContinueClick() { @@ -48,6 +52,13 @@ class BlazeCampaignCreationIntroViewModel @Inject constructor( } } + init { + analyticsTracker.track( + stat = BLAZE_INTRO_DISPLAYED, + properties = mapOf(AnalyticsTracker.KEY_BLAZE_SOURCE to navArgs.source.trackingName) + ) + } + fun onDismissClick() { triggerEvent(Exit) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt index e869413ce1a..c7c52004f78 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt @@ -14,6 +14,7 @@ import com.woocommerce.android.R import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.base.TopLevelFragment +import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.main.AppBarStatus @@ -114,7 +115,7 @@ class MoreMenuFragment : TopLevelFragment() { private fun openBlazeCreationFlow() { lifecycleScope.launch { - blazeCampaignCreationDispatcher.startCampaignCreation() + blazeCampaignCreationDispatcher.startCampaignCreation(source = BlazeFlowSource.MORE_MENU_ITEM) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt index be3ea3d2742..7b0c7366967 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt @@ -279,7 +279,10 @@ class MyStoreFragment : private fun openBlazeCreationFlow(productId: Long?) { lifecycleScope.launch { - blazeCampaignCreationDispatcher.startCampaignCreation(productId) + blazeCampaignCreationDispatcher.startCampaignCreation( + source = BlazeFlowSource.MY_STORE_SECTION, + productId = productId + ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt index 638676f60ab..2d3f335d9c6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt @@ -46,6 +46,7 @@ import com.woocommerce.android.model.Product.Image import com.woocommerce.android.ui.aztec.AztecEditorFragment import com.woocommerce.android.ui.aztec.AztecEditorFragment.Companion.ARG_AZTEC_EDITOR_TEXT import com.woocommerce.android.ui.aztec.AztecEditorFragment.Companion.ARG_AZTEC_TITLE_FROM_AI_DESCRIPTION +import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.ui.dialog.WooDialog @@ -407,7 +408,10 @@ class ProductDetailFragment : private fun openBlazeCreationFlow(productId: Long) { lifecycleScope.launch { - blazeCampaignCreationDispatcher.startCampaignCreation(productId = productId) + blazeCampaignCreationDispatcher.startCampaignCreation( + source = BlazeFlowSource.PRODUCT_DETAIL_PROMOTE_BUTTON, + productId = productId + ) } } 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 3e386621868..06440fb70e3 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 @@ -14,6 +14,10 @@ android:name="productId" android:defaultValue="-1L" app:argType="long" /> + From 364555011c203cb1a2da50df8117076597fba34b Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 14 Feb 2024 16:12:30 +0100 Subject: [PATCH 111/119] Fix tests --- .../BlazeCampaignCreationDispatcherTests.kt | 37 ++++++++++++++----- ...lazeCampaignCreationIntroViewModelTests.kt | 11 +++++- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcherTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcherTests.kt index a030bf71c89..5236f98b2c1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcherTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcherTests.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.blaze.creation import com.woocommerce.android.ui.blaze.BlazeRepository +import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher.BlazeCampaignCreationDispatcherEvent import com.woocommerce.android.ui.products.ProductListRepository import com.woocommerce.android.ui.products.ProductStatus @@ -39,9 +40,14 @@ class BlazeCampaignCreationDispatcherTests : BaseUnitTest() { } var event: BlazeCampaignCreationDispatcherEvent? = null - dispatcher.startCampaignCreation { event = it } + dispatcher.startCampaignCreation(source = BlazeFlowSource.MY_STORE_SECTION) { event = it } - assertThat(event).isEqualTo(BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationIntro(null)) + assertThat(event).isEqualTo( + BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationIntro( + productId = null, + blazeSource = BlazeFlowSource.MY_STORE_SECTION + ) + ) } @Test @@ -58,7 +64,7 @@ class BlazeCampaignCreationDispatcherTests : BaseUnitTest() { } var event: BlazeCampaignCreationDispatcherEvent? = null - dispatcher.startCampaignCreation { event = it } + dispatcher.startCampaignCreation(source = BlazeFlowSource.MY_STORE_SECTION) { event = it } assertThat(event).isEqualTo(BlazeCampaignCreationDispatcherEvent.ShowProductSelectorScreen) } @@ -77,9 +83,17 @@ class BlazeCampaignCreationDispatcherTests : BaseUnitTest() { } var event: BlazeCampaignCreationDispatcherEvent? = null - dispatcher.startCampaignCreation(productId = 1L) { event = it } - - assertThat(event).isEqualTo(BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm(1L)) + dispatcher.startCampaignCreation( + source = BlazeFlowSource.MY_STORE_SECTION, + productId = 1L + ) { event = it } + + assertThat(event).isEqualTo( + BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm( + productId = 1L, + blazeSource = BlazeFlowSource.MY_STORE_SECTION + ) + ) } @Test @@ -96,8 +110,13 @@ class BlazeCampaignCreationDispatcherTests : BaseUnitTest() { } var event: BlazeCampaignCreationDispatcherEvent? = null - dispatcher.startCampaignCreation { event = it } - - assertThat(event).isEqualTo(BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm(1L)) + dispatcher.startCampaignCreation(source = BlazeFlowSource.MY_STORE_SECTION) { event = it } + + assertThat(event).isEqualTo( + BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm( + productId = 1L, + blazeSource = BlazeFlowSource.MY_STORE_SECTION, + ) + ) } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModelTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModelTests.kt index bf1cc5afe68..4e1b727dd0c 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModelTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModelTests.kt @@ -1,5 +1,7 @@ package com.woocommerce.android.ui.blaze.creation.intro +import com.woocommerce.android.analytics.AnalyticsTrackerWrapper +import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.products.ProductListRepository import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.ui.products.ProductTestUtils @@ -16,15 +18,20 @@ import kotlin.test.Test @OptIn(ExperimentalCoroutinesApi::class) class BlazeCampaignCreationIntroViewModelTests : BaseUnitTest() { private val productListRepository: ProductListRepository = mock() + private val analyticsTracker: AnalyticsTrackerWrapper = mock() private lateinit var viewModel: BlazeCampaignCreationIntroViewModel suspend fun setup(productId: Long, setupMocks: suspend () -> Unit = {}) { setupMocks() viewModel = BlazeCampaignCreationIntroViewModel( - savedStateHandle = BlazeCampaignCreationIntroFragmentArgs(productId).toSavedStateHandle(), + savedStateHandle = BlazeCampaignCreationIntroFragmentArgs( + productId = productId, + source = BlazeFlowSource.MY_STORE_SECTION + ).toSavedStateHandle(), productListRepository = productListRepository, - coroutineDispatchers = coroutinesTestRule.testDispatchers + coroutineDispatchers = coroutinesTestRule.testDispatchers, + analyticsTracker = analyticsTracker, ) } From 6b5bacdd139c73b8e7ba2c357819e10db5c7b037 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 14 Feb 2024 16:30:52 +0100 Subject: [PATCH 112/119] Add analytics tracker as constructor property --- .../blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt index 0dedbf320ae..ce61838fcd0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt @@ -22,9 +22,9 @@ import javax.inject.Inject @HiltViewModel class BlazeCampaignCreationIntroViewModel @Inject constructor( savedStateHandle: SavedStateHandle, + analyticsTracker: AnalyticsTrackerWrapper, private val productListRepository: ProductListRepository, private val coroutineDispatchers: CoroutineDispatchers, - private val analyticsTracker: AnalyticsTrackerWrapper, ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationIntroFragmentArgs by savedStateHandle.navArgs() fun onContinueClick() { From df4f5257c5f87a1ab25fa023f2cee88f22ba4932 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 14 Feb 2024 16:39:42 +0100 Subject: [PATCH 113/119] Add source to Blaze campaign preview screen --- .../blaze/campaigs/BlazeCampaignListFragment.kt | 2 +- .../creation/BlazeCampaignCreationDispatcher.kt | 16 +++++++++++----- .../intro/BlazeCampaignCreationIntroFragment.kt | 3 ++- .../intro/BlazeCampaignCreationIntroViewModel.kt | 14 ++++++++++---- .../BlazeCampaignCreationPreviewViewModel.kt | 8 ++++++++ .../android/ui/moremenu/MoreMenuFragment.kt | 2 +- .../android/ui/mystore/MyStoreFragment.kt | 2 +- .../android/ui/products/ProductDetailFragment.kt | 2 +- .../nav_graph_blaze_campaign_creation.xml | 4 ++++ 9 files changed, 39 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt index bb28dd2135c..4f2e369ea91 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/campaigs/BlazeCampaignListFragment.kt @@ -49,7 +49,7 @@ class BlazeCampaignListFragment : BaseFragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - blazeCampaignCreationDispatcher.attachFragment(this) + blazeCampaignCreationDispatcher.attachFragment(this, BlazeFlowSource.CAMPAIGN_LIST) viewModel.event.observe(viewLifecycleOwner) { event -> when (event) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt index 9580510c71d..55d16f8b6ac 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt @@ -36,10 +36,13 @@ class BlazeCampaignCreationDispatcher @Inject constructor( ) { private var fragmentReference: WeakReference = WeakReference(null) - fun attachFragment(fragment: BaseFragment) { + fun attachFragment(fragment: BaseFragment, source: BlazeFlowSource) { this.fragmentReference = WeakReference(fragment) fragment.handleResult>(ProductSelectorFragment.PRODUCT_SELECTOR_RESULT) { - this.fragmentReference.get()?.showCampaignPreview(it.first().id) + this.fragmentReference.get()?.showCampaignPreview( + productId = it.first().id, + source = source + ) } } @@ -103,7 +106,7 @@ class BlazeCampaignCreationDispatcher @Inject constructor( ?.showIntro(event.productId, event.blazeSource) is BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationForm -> fragmentReference.get() - ?.showCampaignPreview(event.productId) + ?.showCampaignPreview(event.productId, event.blazeSource) is BlazeCampaignCreationDispatcherEvent.ShowProductSelectorScreen -> fragmentReference.get() ?.showProductSelector() @@ -120,10 +123,13 @@ class BlazeCampaignCreationDispatcher @Inject constructor( ) } - private fun BaseFragment.showCampaignPreview(productId: Long) { + private fun BaseFragment.showCampaignPreview(productId: Long, source: BlazeFlowSource) { findNavController().navigateToBlazeGraph( startDestination = R.id.blazeCampaignCreationPreviewFragment, - bundle = BlazeCampaignCreationPreviewFragmentArgs(productId).toBundle() + bundle = BlazeCampaignCreationPreviewFragmentArgs( + productId = productId, + source = source + ).toBundle() ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroFragment.kt index f9d9eb11b38..746889b3af1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroFragment.kt @@ -44,7 +44,8 @@ class BlazeCampaignCreationIntroFragment : BaseFragment() { findNavController().navigateSafely( directions = BlazeCampaignCreationIntroFragmentDirections .actionBlazeCampaignCreationIntroFragmentToBlazeCampaignCreationPreviewFragment( - productId = event.productId + productId = event.productId, + source = event.source ), navOptions = navOptions { popUpTo(R.id.blazeCampaignCreationIntroFragment) { inclusive = true } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt index ce61838fcd0..faa0ac91f3d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_INTRO_DISPLAYED import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTrackerWrapper +import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.products.ProductListRepository import com.woocommerce.android.ui.products.ProductStatus import com.woocommerce.android.util.CoroutineDispatchers @@ -37,11 +38,16 @@ class BlazeCampaignCreationIntroViewModel @Inject constructor( launch { if (navArgs.productId != -1L) { - triggerEvent(ShowCampaignCreationForm(navArgs.productId)) + triggerEvent(ShowCampaignCreationForm(navArgs.productId, BlazeFlowSource.INTRO_VIEW)) } else { val products = getPublishedProducts() when { - products.size == 1 -> triggerEvent(ShowCampaignCreationForm(products.first().remoteId)) + products.size == 1 -> triggerEvent( + ShowCampaignCreationForm( + products.first().remoteId, BlazeFlowSource.INTRO_VIEW + ) + ) + products.isNotEmpty() -> triggerEvent(ShowProductSelector) else -> { WooLog.w(WooLog.T.BLAZE, "No products available to create a campaign") @@ -64,9 +70,9 @@ class BlazeCampaignCreationIntroViewModel @Inject constructor( } fun onProductSelected(productId: Long) { - triggerEvent(ShowCampaignCreationForm(productId)) + triggerEvent(ShowCampaignCreationForm(productId, BlazeFlowSource.INTRO_VIEW)) } object ShowProductSelector : MultiLiveEvent.Event() - data class ShowCampaignCreationForm(val productId: Long) : MultiLiveEvent.Event() + data class ShowCampaignCreationForm(val productId: Long, val source: BlazeFlowSource) : MultiLiveEvent.Event() } 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 e5bb642f0f5..c65b8b99ffe 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,9 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.woocommerce.android.R.string +import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_ENTRY_POINT_TAPPED +import com.woocommerce.android.analytics.AnalyticsTracker +import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.extensions.combine import com.woocommerce.android.extensions.formatToMMMdd import com.woocommerce.android.ui.blaze.BlazeRepository @@ -40,6 +43,7 @@ import kotlin.time.Duration.Companion.days @HiltViewModel class BlazeCampaignCreationPreviewViewModel @Inject constructor( savedStateHandle: SavedStateHandle, + analyticsTrackerWrapper: AnalyticsTrackerWrapper, private val blazeRepository: BlazeRepository, private val resourceProvider: ResourceProvider, private val currencyFormatter: CurrencyFormatter @@ -110,6 +114,10 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( init { loadData() + analyticsTrackerWrapper.track( + stat = BLAZE_ENTRY_POINT_TAPPED, + properties = mapOf(AnalyticsTracker.KEY_BLAZE_SOURCE to navArgs.source.trackingName) + ) } fun onBackPressed() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt index c7c52004f78..1d0bb315174 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt @@ -76,7 +76,7 @@ class MoreMenuFragment : TopLevelFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - blazeCampaignCreationDispatcher.attachFragment(this) + blazeCampaignCreationDispatcher.attachFragment(this, BlazeFlowSource.MORE_MENU_ITEM) setupObservers() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt index 7b0c7366967..2aba9edaae1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/mystore/MyStoreFragment.kt @@ -176,7 +176,7 @@ class MyStoreFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) - blazeCampaignCreationDispatcher.attachFragment(this) + blazeCampaignCreationDispatcher.attachFragment(this, BlazeFlowSource.MY_STORE_SECTION) _binding = FragmentMyStoreBinding.bind(view) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt index 2d3f335d9c6..6c6df34c7e2 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/ProductDetailFragment.kt @@ -160,7 +160,7 @@ class ProductDetailFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - blazeCampaignCreationDispatcher.attachFragment(this) + blazeCampaignCreationDispatcher.attachFragment(this, BlazeFlowSource.PRODUCT_DETAIL_PROMOTE_BUTTON) _binding = FragmentProductDetailBinding.bind(view) requireActivity().addMenuProvider(this, viewLifecycleOwner) 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 06440fb70e3..78413e9af3e 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 @@ -54,6 +54,10 @@ android:name="productId" android:defaultValue="-1L" app:argType="long" /> + From 5e1b9193c720406d2ecf95666b518535833df2af Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 14 Feb 2024 09:52:50 -0600 Subject: [PATCH 114/119] update ui based on design feedback --- .../ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt | 2 +- WooCommerce/src/main/res/menu/menu_analytics_settings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt index 4594d59f5e5..002f3aab684 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/analytics/hub/settings/AnalyticsHubSettingScreen.kt @@ -130,7 +130,7 @@ fun AnalyticCardItem( onSelectionChange = { onSelectionChange(id, !isSelected) } ) Text( - text = title.uppercase(), + text = title, modifier .weight(2f) .padding(horizontal = 16.dp) diff --git a/WooCommerce/src/main/res/menu/menu_analytics_settings.xml b/WooCommerce/src/main/res/menu/menu_analytics_settings.xml index ee695f4c382..7431dcc8ddc 100644 --- a/WooCommerce/src/main/res/menu/menu_analytics_settings.xml +++ b/WooCommerce/src/main/res/menu/menu_analytics_settings.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> From f38233ac52f5014bdf9eb63b6f0da4b73eddab16 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 14 Feb 2024 18:14:41 +0100 Subject: [PATCH 115/119] Bump fluxc --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3ddd2585de1..53796a6c00e 100644 --- a/build.gradle +++ b/build.gradle @@ -97,7 +97,7 @@ tasks.register("installGitHooks", Copy) { } ext { - fluxCVersion = '2954-b6a6f03406478755900160b8dc52de6ea593986c' + fluxCVersion = 'trunk-3a4c421cb0a18898c23dc95fef044e991b16868d' glideVersion = '4.16.0' coilVersion = '2.1.0' constraintLayoutVersion = '1.2.0' From 7306d92361d1b69079be083df6e12ec16ccca818 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 14 Feb 2024 18:15:15 +0100 Subject: [PATCH 116/119] Fix mapping to domain model --- .../kotlin/com/woocommerce/android/ui/blaze/BlazeRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9374a06e1c9..553c5f62119 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 @@ -246,7 +246,7 @@ class BlazeRepository @Inject constructor( mainImage = image, targetingParameters = campaignDetails.targetingParameters.let { BlazeTargetingParameters( - locations = it.locations.map { location -> location.id.toString() }, + locations = it.locations.map { location -> location.id }, languages = it.languages.map { language -> language.code }, devices = it.devices.map { device -> device.id }, topics = it.interests.map { interest -> interest.id } From 9a01535e78baf6f533ea08da00ae062fa1bc2e4f Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Wed, 14 Feb 2024 18:52:12 +0100 Subject: [PATCH 117/119] Move blaze entry point tapped event to the place where tap happens --- .../creation/BlazeCampaignCreationDispatcher.kt | 14 ++++++++++++-- .../intro/BlazeCampaignCreationIntroViewModel.kt | 7 ++++++- .../BlazeCampaignCreationPreviewViewModel.kt | 8 -------- .../BlazeCampaignCreationDispatcherTests.kt | 5 ++++- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt index 55d16f8b6ac..35cba68db23 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcher.kt @@ -6,6 +6,9 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.navOptions import com.woocommerce.android.R import com.woocommerce.android.R.string +import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_ENTRY_POINT_TAPPED +import com.woocommerce.android.analytics.AnalyticsTracker +import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.extensions.handleResult import com.woocommerce.android.extensions.navigateSafely import com.woocommerce.android.ui.base.BaseFragment @@ -32,7 +35,8 @@ import javax.inject.Inject class BlazeCampaignCreationDispatcher @Inject constructor( private val blazeRepository: BlazeRepository, private val productListRepository: ProductListRepository, - private val coroutineDispatchers: CoroutineDispatchers + private val coroutineDispatchers: CoroutineDispatchers, + private val analyticsTracker: AnalyticsTrackerWrapper, ) { private var fragmentReference: WeakReference = WeakReference(null) @@ -56,7 +60,13 @@ class BlazeCampaignCreationDispatcher @Inject constructor( BlazeCampaignCreationDispatcherEvent.ShowBlazeCampaignCreationIntro(productId, source) ) - else -> startCampaignCreationWithoutIntro(productId, source, handler) + else -> { + analyticsTracker.track( + stat = BLAZE_ENTRY_POINT_TAPPED, + properties = mapOf(AnalyticsTracker.KEY_BLAZE_SOURCE to source.trackingName) + ) + startCampaignCreationWithoutIntro(productId, source, handler) + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt index faa0ac91f3d..80be40de247 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModel.kt @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.blaze.creation.intro import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_ENTRY_POINT_TAPPED import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_INTRO_DISPLAYED import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTrackerWrapper @@ -23,13 +24,17 @@ import javax.inject.Inject @HiltViewModel class BlazeCampaignCreationIntroViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - analyticsTracker: AnalyticsTrackerWrapper, private val productListRepository: ProductListRepository, private val coroutineDispatchers: CoroutineDispatchers, + private val analyticsTracker: AnalyticsTrackerWrapper, ) : ScopedViewModel(savedStateHandle) { private val navArgs: BlazeCampaignCreationIntroFragmentArgs by savedStateHandle.navArgs() fun onContinueClick() { suspend fun getPublishedProducts() = withContext(coroutineDispatchers.io) { + analyticsTracker.track( + stat = BLAZE_ENTRY_POINT_TAPPED, + properties = mapOf(AnalyticsTracker.KEY_BLAZE_SOURCE to BlazeFlowSource.INTRO_VIEW.trackingName) + ) productListRepository.getProductList( productFilterOptions = mapOf(ProductFilterOption.STATUS to ProductStatus.PUBLISH.value), sortType = ProductSorting.DATE_DESC, 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 c65b8b99ffe..e5bb642f0f5 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,9 +5,6 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.woocommerce.android.R.string -import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_ENTRY_POINT_TAPPED -import com.woocommerce.android.analytics.AnalyticsTracker -import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.extensions.combine import com.woocommerce.android.extensions.formatToMMMdd import com.woocommerce.android.ui.blaze.BlazeRepository @@ -43,7 +40,6 @@ import kotlin.time.Duration.Companion.days @HiltViewModel class BlazeCampaignCreationPreviewViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - analyticsTrackerWrapper: AnalyticsTrackerWrapper, private val blazeRepository: BlazeRepository, private val resourceProvider: ResourceProvider, private val currencyFormatter: CurrencyFormatter @@ -114,10 +110,6 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( init { loadData() - analyticsTrackerWrapper.track( - stat = BLAZE_ENTRY_POINT_TAPPED, - properties = mapOf(AnalyticsTracker.KEY_BLAZE_SOURCE to navArgs.source.trackingName) - ) } fun onBackPressed() { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcherTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcherTests.kt index 5236f98b2c1..67119069280 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcherTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/BlazeCampaignCreationDispatcherTests.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.blaze.creation +import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.ui.blaze.BlazeRepository import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher.BlazeCampaignCreationDispatcherEvent @@ -19,6 +20,7 @@ import org.wordpress.android.fluxc.store.WCProductStore.ProductSorting class BlazeCampaignCreationDispatcherTests : BaseUnitTest() { private val productListRepository: ProductListRepository = mock() private val blazeRepository: BlazeRepository = mock() + private val analyticsTracker: AnalyticsTrackerWrapper = mock() private lateinit var dispatcher: BlazeCampaignCreationDispatcher @@ -29,7 +31,8 @@ class BlazeCampaignCreationDispatcherTests : BaseUnitTest() { dispatcher = BlazeCampaignCreationDispatcher( blazeRepository = blazeRepository, productListRepository = productListRepository, - coroutineDispatchers = coroutinesTestRule.testDispatchers + coroutineDispatchers = coroutinesTestRule.testDispatchers, + analyticsTracker = analyticsTracker ) } From f2a9c883dbb71823eae6f95d1ffb4d77e1c4ac04 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Thu, 15 Feb 2024 00:58:07 +0100 Subject: [PATCH 118/119] Fix compilation issues for unit tests --- ...lazeCampaignCreationIntroViewModelTests.kt | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModelTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModelTests.kt index 4e1b727dd0c..a1e8c2e04de 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModelTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/blaze/creation/intro/BlazeCampaignCreationIntroViewModelTests.kt @@ -42,7 +42,12 @@ class BlazeCampaignCreationIntroViewModelTests : BaseUnitTest() { viewModel.onContinueClick() val event = viewModel.event.value - assertThat(event).isEqualTo(BlazeCampaignCreationIntroViewModel.ShowCampaignCreationForm(1L)) + assertThat(event).isEqualTo( + BlazeCampaignCreationIntroViewModel.ShowCampaignCreationForm( + productId = 1L, + source = BlazeFlowSource.INTRO_VIEW + ) + ) } @Test @@ -60,7 +65,12 @@ class BlazeCampaignCreationIntroViewModelTests : BaseUnitTest() { viewModel.onContinueClick() val event = viewModel.event.value - assertThat(event).isEqualTo(BlazeCampaignCreationIntroViewModel.ShowCampaignCreationForm(1L)) + assertThat(event).isEqualTo( + BlazeCampaignCreationIntroViewModel.ShowCampaignCreationForm( + productId = 1L, + source = BlazeFlowSource.INTRO_VIEW + ) + ) } @Test @@ -88,7 +98,12 @@ class BlazeCampaignCreationIntroViewModelTests : BaseUnitTest() { viewModel.onProductSelected(1L) val event = viewModel.event.value - assertThat(event).isEqualTo(BlazeCampaignCreationIntroViewModel.ShowCampaignCreationForm(1L)) + assertThat(event).isEqualTo( + BlazeCampaignCreationIntroViewModel.ShowCampaignCreationForm( + productId = 1L, + source = BlazeFlowSource.INTRO_VIEW + ) + ) } @Test From b1d8bc97b8745aba036c1b6abc8c9d2470a4f754 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 15 Feb 2024 12:39:15 +0100 Subject: [PATCH 119/119] Pass targetingParameters when calculating ad forecast --- .../android/ui/blaze/BlazeRepository.kt | 19 ++++++++++++++----- .../budget/BlazeCampaignBudgetViewModel.kt | 3 ++- .../BlazeCampaignCreationPreviewFragment.kt | 5 ++++- .../BlazeCampaignCreationPreviewViewModel.kt | 5 +++-- .../nav_graph_blaze_campaign_creation.xml | 3 +++ 5 files changed, 26 insertions(+), 9 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 553c5f62119..0da2ec3ff1f 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 @@ -160,13 +160,22 @@ class BlazeRepository @Inject constructor( suspend fun fetchAdForecast( startDate: Date, campaignDurationDays: Int, - totalBudget: Float + totalBudget: Float, + targetingParameters: TargetingParameters ): Result { val result = blazeCampaignsStore.fetchBlazeAdForecast( - selectedSite.get(), - startDate, - Date(startDate.time + campaignDurationDays.days.inWholeMilliseconds), - totalBudget.roundToInt().toDouble(), + siteModel = selectedSite.get(), + startDate = startDate, + endDate = Date(startDate.time + campaignDurationDays.days.inWholeMilliseconds), + totalBudget = totalBudget.roundToInt().toDouble(), + targetingParameters = targetingParameters.let { + BlazeTargetingParameters( + locations = it.locations.map { location -> location.id }, + languages = it.languages.map { language -> language.code }, + devices = it.devices.map { device -> device.id }, + topics = it.interests.map { interest -> interest.id } + ) + } ) return when { result.isError -> { 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 681dc53a25e..fa833361ff6 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 @@ -158,7 +158,8 @@ class BlazeCampaignBudgetViewModel @Inject constructor( repository.fetchAdForecast( startDate = Date(budgetUiState.value.campaignStartDateMillis), campaignDurationDays = budgetUiState.value.durationInDays, - totalBudget = budgetUiState.value.totalBudget + totalBudget = budgetUiState.value.totalBudget, + targetingParameters = navArgs.targetingParameters ).onSuccess { fetchAdForecastResult -> campaignForecastState = campaignForecastState.copy( isLoading = false, 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 8ae2503a02f..2d950274f1d 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 @@ -58,7 +58,10 @@ class BlazeCampaignCreationPreviewFragment : BaseFragment() { is NavigateToBudgetScreen -> findNavController().navigateSafely( BlazeCampaignCreationPreviewFragmentDirections - .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignBudgetFragment(event.budget) + .actionBlazeCampaignCreationPreviewFragmentToBlazeCampaignBudgetFragment( + budget = event.budget, + targetingParameters = event.targetingParameters + ) ) 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 46a7d4d9c09..c29c2f7708b 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 @@ -163,7 +163,7 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( displayTitle = resourceProvider.getString(string.blaze_campaign_preview_details_budget), displayValue = budget.toDisplayValue(), onItemSelected = { - triggerEvent(NavigateToBudgetScreen(budget)) + triggerEvent(NavigateToBudgetScreen(budget, targetingParameters)) }, ), targetDetails = listOf( @@ -263,7 +263,8 @@ class BlazeCampaignCreationPreviewViewModel @Inject constructor( ) data class NavigateToBudgetScreen( - val budget: BlazeRepository.Budget + val budget: BlazeRepository.Budget, + val targetingParameters: BlazeRepository.TargetingParameters ) : MultiLiveEvent.Event() data class NavigateToAdDestinationScreen( 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 ba03e08cea1..e03e45db739 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 @@ -95,6 +95,9 @@ +