Skip to content

Commit

Permalink
Merge pull request #12648 from woocommerce/issue/12494-blaze-webview-…
Browse files Browse the repository at this point in the history
…fragment

Issue/12494 blaze webview fragment
  • Loading branch information
JorgeMucientes authored Sep 25, 2024
2 parents 025fca0 + fbb92ac commit ee71f87
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 55 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
20.6
-----
- [*] Users can set a password to protect products when signed in using site credentials (compatible with WooCommerce 8.1.0 and higher) [https://github.com/woocommerce/woocommerce-android/pull/12642]
- [*] When promoting a Blaze product from campaign detail webview, the native flow will be triggered [https://github.com/woocommerce/woocommerce-android/pull/12648]

20.5
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ class BlazeUrlsHelper @Inject constructor(
) {
companion object {
private const val HTTP_PATTERN = "(https?://)"
private const val BASE_URL = "https://wordpress.com/advertising"
const val BASE_URL = "https://wordpress.com/advertising"
const val PROMOTE_AGAIN_URL_PATH = "blazepress-widget=post"
}

fun buildCampaignsListUrl(): String = "$BASE_URL/campaigns/${getSiteUrl()}"

fun buildCampaignDetailsUrl(campaignId: String): String = "$BASE_URL/campaigns/$campaignId/${getSiteUrl()}"

fun getCampaignStopUrlPath(campaignId: String): String = "/campaigns/$campaignId/stop"

private fun getSiteUrl() = selectedSite.get().url.replace(Regex(HTTP_PATTERN), "")

fun extractProductIdFromPromoteAgainUrl(url: String): Long? =
url.substringAfter("post-")
.substringBefore("_campaign")
.toLongOrNull()

enum class BlazeFlowSource(val trackingName: String) {
MORE_MENU_ITEM("menu"),
PRODUCT_DETAIL_PROMOTE_BUTTON("product_detail_promote_button"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.woocommerce.android.NavGraphMainDirections
import com.woocommerce.android.R
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.BlazeUrlsHelper.BlazeFlowSource
import com.woocommerce.android.ui.blaze.creation.BlazeCampaignCreationDispatcher
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewFragment
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
import com.woocommerce.android.ui.main.AppBarStatus
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
Expand Down Expand Up @@ -54,27 +57,32 @@ class BlazeCampaignListFragment : BaseFragment() {
when (event) {
is Exit -> findNavController().popBackStack()
is BlazeCampaignListViewModel.LaunchBlazeCampaignCreation -> openBlazeCreationFlow()
is BlazeCampaignListViewModel.ShowCampaignDetails -> openCampaignDetails(
event.url,
event.urlToTriggerExit
)
is BlazeCampaignListViewModel.ShowCampaignDetails -> openCampaignDetails(event.campaignId)
is BlazeCampaignListViewModel.LaunchBlazeCampaignCreationForProduct ->
openBlazeCreationFlow(event.productId)
}
}
handleResults()
}

private fun openBlazeCreationFlow() {
private fun openBlazeCreationFlow(productId: Long? = null) {
lifecycleScope.launch {
blazeCampaignCreationDispatcher.startCampaignCreation(source = BlazeFlowSource.CAMPAIGN_LIST)
blazeCampaignCreationDispatcher.startCampaignCreation(
source = BlazeFlowSource.CAMPAIGN_LIST,
productId = productId
)
}
}

private fun openCampaignDetails(url: String, urlToTriggerExit: String) {
private fun openCampaignDetails(url: String) {
findNavController().navigateSafely(
NavGraphMainDirections.actionGlobalWPComWebViewFragment(
urlToLoad = url,
urlsToTriggerExit = arrayOf(urlToTriggerExit),
title = getString(R.string.blaze_campaign_details_title)
)
NavGraphMainDirections.actionGlobalBlazeCampaignDetailWebViewFragment(campaignId = url)
)
}

private fun handleResults() {
handleResult<BlazeAction>(BlazeCampaignDetailWebViewFragment.BLAZE_WEBVIEW_RESULT) {
viewModel.onBlazeCampaignWebViewAction(it)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
import com.woocommerce.android.extensions.NumberExtensionsWrapper
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.blaze.BlazeCampaignUi
import com.woocommerce.android.ui.blaze.BlazeUrlsHelper
import com.woocommerce.android.ui.blaze.BlazeUrlsHelper.BlazeFlowSource
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.CampaignStopped
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.None
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.PromoteProductAgain
import com.woocommerce.android.ui.blaze.toUiState
import com.woocommerce.android.util.CurrencyFormatter
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event
Expand All @@ -34,7 +37,6 @@ class BlazeCampaignListViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val blazeCampaignsStore: BlazeCampaignsStore,
private val selectedSite: SelectedSite,
private val blazeUrlsHelper: BlazeUrlsHelper,
private val appPrefsWrapper: AppPrefsWrapper,
private val analyticsTrackerWrapper: AnalyticsTrackerWrapper,
private val currencyFormatter: CurrencyFormatter,
Expand Down Expand Up @@ -81,10 +83,7 @@ class BlazeCampaignListViewModel @Inject constructor(
}
if (navArgs.campaignId != null) {
triggerEvent(
ShowCampaignDetails(
url = blazeUrlsHelper.buildCampaignDetailsUrl(navArgs.campaignId!!),
urlToTriggerExit = blazeUrlsHelper.buildCampaignsListUrl()
)
ShowCampaignDetails(campaignId = navArgs.campaignId!!)
)
}
launch {
Expand Down Expand Up @@ -122,10 +121,7 @@ class BlazeCampaignListViewModel @Inject constructor(
properties = mapOf(AnalyticsTracker.KEY_BLAZE_SOURCE to BlazeFlowSource.CAMPAIGN_LIST.trackingName)
)
triggerEvent(
ShowCampaignDetails(
url = blazeUrlsHelper.buildCampaignDetailsUrl(campaignId),
urlToTriggerExit = blazeUrlsHelper.buildCampaignsListUrl()
)
ShowCampaignDetails(campaignId)
)
}

Expand All @@ -140,6 +136,20 @@ class BlazeCampaignListViewModel @Inject constructor(
}
}

fun onBlazeCampaignWebViewAction(action: BlazeCampaignDetailWebViewViewModel.BlazeAction) {
when (action) {
CampaignStopped -> launch { loadCampaigns(offset = 0) }
is PromoteProductAgain -> triggerEvent(
LaunchBlazeCampaignCreationForProduct(
productId = action.productId,
source = BlazeFlowSource.CAMPAIGN_LIST
)
)

None -> Unit // Do nothing
}
}

data class BlazeCampaignListState(
val campaigns: List<ClickableCampaign>,
val onAddNewCampaignClicked: () -> Unit,
Expand All @@ -153,8 +163,10 @@ class BlazeCampaignListViewModel @Inject constructor(
)

data class LaunchBlazeCampaignCreation(val source: BlazeFlowSource) : Event()
data class ShowCampaignDetails(
val url: String,
val urlToTriggerExit: String
data class LaunchBlazeCampaignCreationForProduct(
val productId: Long?,
val source: BlazeFlowSource,
) : Event()

data class ShowCampaignDetails(val campaignId: String) : Event()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.woocommerce.android.ui.blaze.detail

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.extensions.navigateBackWithResult
import com.woocommerce.android.ui.base.BaseFragment
import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewAuthenticator
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
import com.woocommerce.android.ui.main.AppBarStatus
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult
import dagger.hilt.android.AndroidEntryPoint
import org.wordpress.android.fluxc.network.UserAgent
import javax.inject.Inject

@AndroidEntryPoint
class BlazeCampaignDetailWebViewFragment : BaseFragment() {
companion object {
const val BLAZE_WEBVIEW_RESULT = "blaze-webview-result"
}

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

private val viewModel: BlazeCampaignDetailWebViewViewModel by viewModels()

@Inject
lateinit var wpComAuthenticator: WPComWebViewAuthenticator

@Inject
lateinit var userAgent: UserAgent

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)

setContent {
WooThemeWithBackground {
BlazeCampaignDetailWebViewScreen(
viewModel = viewModel,
wpComAuthenticator = wpComAuthenticator,
userAgent = userAgent,
onUrlLoaded = viewModel::onUrlLoaded,
onDismiss = viewModel::onDismiss
)
}
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.event.observe(viewLifecycleOwner) { event ->
when (event) {
is Exit -> findNavController().popBackStack()
is ExitWithResult<*> -> navigateBackWithResult(
BLAZE_WEBVIEW_RESULT,
event.data
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.woocommerce.android.ui.blaze.detail

import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.woocommerce.android.R
import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewAuthenticator
import com.woocommerce.android.ui.compose.component.Toolbar
import com.woocommerce.android.ui.compose.component.web.WCWebView
import org.wordpress.android.fluxc.network.UserAgent

@Composable
fun BlazeCampaignDetailWebViewScreen(
viewModel: BlazeCampaignDetailWebViewViewModel,
wpComAuthenticator: WPComWebViewAuthenticator,
userAgent: UserAgent,
onUrlLoaded: (String) -> Unit,
onDismiss: () -> Unit,
) {
Scaffold(
topBar = {
Toolbar(
title = stringResource(id = R.string.blaze_campaign_details_title),
onNavigationButtonClick = onDismiss
)
}
) { paddingValues ->
WCWebView(
url = viewModel.viewState.urlToLoad,
userAgent = userAgent,
wpComAuthenticator = wpComAuthenticator,
onUrlLoaded = onUrlLoaded,
clearCache = true,
modifier = Modifier.padding(paddingValues)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.woocommerce.android.ui.blaze.detail

import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import com.woocommerce.android.ui.blaze.BlazeUrlsHelper
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.CampaignStopped
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.None
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.PromoteProductAgain
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ExitWithResult
import com.woocommerce.android.viewmodel.ScopedViewModel
import com.woocommerce.android.viewmodel.navArgs
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.parcelize.Parcelize
import javax.inject.Inject

@HiltViewModel
class BlazeCampaignDetailWebViewViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val blazeUrlsHelper: BlazeUrlsHelper
) : ScopedViewModel(savedStateHandle) {
private val navArgs: BlazeCampaignDetailWebViewFragmentArgs by savedStateHandle.navArgs()
private val stopUrlPath = blazeUrlsHelper.getCampaignStopUrlPath(navArgs.campaignId)
private val blazeCampaignListUrl = blazeUrlsHelper.buildCampaignsListUrl()

var viewState = ViewState(
urlToLoad = blazeUrlsHelper.buildCampaignDetailsUrl(navArgs.campaignId),
blazeAction = None
)

fun onUrlLoaded(url: String) {
when {
blazeCampaignListUrl == url -> onDismiss()
url.contains(stopUrlPath) -> {
viewState = viewState.copy(blazeAction = CampaignStopped)
}

url.contains(BlazeUrlsHelper.PROMOTE_AGAIN_URL_PATH) -> {
viewState = viewState.copy(
blazeAction = PromoteProductAgain(
productId = blazeUrlsHelper.extractProductIdFromPromoteAgainUrl(url)
)
)
onDismiss()
}
}
}

fun onDismiss() {
when (viewState.blazeAction) {
None -> triggerEvent(Exit)
else -> triggerEvent(ExitWithResult(viewState.blazeAction))
}
}

data class ViewState(
val urlToLoad: String,
val blazeAction: BlazeAction,
)

@Parcelize
sealed interface BlazeAction : Parcelable {
@Parcelize
data object CampaignStopped : BlazeAction

@Parcelize
data class PromoteProductAgain(val productId: Long?) : BlazeAction

@Parcelize
data object None : BlazeAction
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.woocommerce.android.analytics.AnalyticsTracker
import com.woocommerce.android.databinding.FragmentDashboardBinding
import com.woocommerce.android.extensions.getColorCompat
import com.woocommerce.android.extensions.handleNotice
import com.woocommerce.android.extensions.handleResult
import com.woocommerce.android.extensions.navigateSafely
import com.woocommerce.android.extensions.scrollStartEvents
import com.woocommerce.android.extensions.showDateRangePicker
Expand All @@ -39,6 +40,11 @@ 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.blaze.detail.BlazeCampaignDetailWebViewFragment
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.CampaignStopped
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.None
import com.woocommerce.android.ui.blaze.detail.BlazeCampaignDetailWebViewViewModel.BlazeAction.PromoteProductAgain
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.ContactSupport
import com.woocommerce.android.ui.dashboard.DashboardViewModel.DashboardEvent.FeedbackNegativeAction
Expand Down Expand Up @@ -219,6 +225,19 @@ class DashboardFragment :
handleNotice(GoogleAdsWebViewFragment.WEBVIEW_RESULT) {
navigateToGoogleAdsCreationSuccess()
}
handleResult<BlazeAction>(BlazeCampaignDetailWebViewFragment.BLAZE_WEBVIEW_RESULT) {
when (it) {
None,
CampaignStopped -> Unit // We don't need to handle actions here
is PromoteProductAgain ->
viewLifecycleOwner.lifecycleScope.launch {
blazeCampaignCreationDispatcher.startCampaignCreation(
BlazeFlowSource.MY_STORE_SECTION,
it.productId
)
}
}
}
}

private fun onVisitorStatsUnavailable(jetpackBenefitsBanner: DashboardViewModel.JetpackBenefitsBannerUiModel?) {
Expand Down
Loading

0 comments on commit ee71f87

Please sign in to comment.