Skip to content

Commit

Permalink
Merge pull request #10762 from woocommerce/issue/10737-ad-url-params
Browse files Browse the repository at this point in the history
Blaze: Ad destination parameters
  • Loading branch information
JorgeMucientes authored Feb 13, 2024
2 parents f4ea250 + 7c63302 commit c2ab3b0
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String>(BLAZE_DESTINATION_PARAMETERS_RESULT) { url ->
viewModel.onTargetUrlUpdated(url)
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
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(key = "header") {
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 -> "key${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(key = "footer") {
Column(
modifier = Modifier
.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
),
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 = {}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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
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 = 2096
}
private val navArgs: BlazeCampaignCreationAdDestinationParametersFragmentArgs by savedStateHandle.navArgs()

private val _viewState = MutableStateFlow(
ViewState(
baseUrl = navArgs.url.getBaseUrl(),
parameters = navArgs.url.parseParameters()
)
)

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)
}
}

data class ViewState(
private val baseUrl: String,
val parameters: Map<String, String>
) {
val url by lazy {
parameters.joinToUrl(baseUrl)
}

val charactersRemaining: Int
get() = MAX_CHARACTERS - parameters.entries.joinToString("&").length
}
}
Loading

0 comments on commit c2ab3b0

Please sign in to comment.