Skip to content

Commit

Permalink
Merge pull request #12601 from woocommerce/issue/12597-custom-fields-…
Browse files Browse the repository at this point in the history
…banner

Issue/12597 custom fields banner
  • Loading branch information
hichamboushaba authored Sep 25, 2024
2 parents 3d6cab5 + 6a5e7c7 commit cdf6e5b
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ object AppPrefs {
CHA_CHING_SOUND_ISSUE_DIALOG_DISMISSED,
TIMES_AI_PRODUCT_CREATION_SURVEY_DISPLAYED,
AI_PRODUCT_CREATION_SURVEY_DISMISSED,
CUSTOM_FIELDS_TOP_BANNER_DISMISSED,
}

/**
Expand Down Expand Up @@ -269,6 +270,10 @@ object AppPrefs {
get() = getBoolean(DeletablePrefKey.CHA_CHING_SOUND_ISSUE_DIALOG_DISMISSED, false)
set(value) = setBoolean(DeletablePrefKey.CHA_CHING_SOUND_ISSUE_DIALOG_DISMISSED, value)

var isCustomFieldsTopBannerDismissed: Boolean
get() = getBoolean(DeletablePrefKey.CUSTOM_FIELDS_TOP_BANNER_DISMISSED, false)
set(value) = setBoolean(DeletablePrefKey.CUSTOM_FIELDS_TOP_BANNER_DISMISSED, value)

fun getProductSortingChoice(currentSiteId: Int) = getString(getProductSortingKey(currentSiteId)).orNullIfEmpty()

fun setProductSortingChoice(currentSiteId: Int, value: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class AppPrefsWrapper @Inject constructor() {

var isAiProductCreationSurveyDismissed by AppPrefs::isAiProductCreationSurveyDismissed

var isCustomFieldsTopBannerDismissed by AppPrefs::isCustomFieldsTopBannerDismissed

fun getAppInstallationDate() = AppPrefs.installationDate

fun getReceiptUrl(localSiteId: Int, remoteSiteId: Long, selfHostedSiteId: Long, orderId: Long) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.woocommerce.android.ui.compose.component

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.Card
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Campaign
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.woocommerce.android.R
import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground

@Composable
fun ExpandableTopBanner(
title: String,
message: String,
buttons: @Composable RowScope.() -> Unit,
modifier: Modifier = Modifier,
expandedByDefault: Boolean = false,
) {
Card(
shape = RectangleShape,
modifier = modifier
) {
var isExpanded by rememberSaveable { mutableStateOf(expandedByDefault) }

Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable { isExpanded = !isExpanded }
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Icon(
imageVector = Icons.Default.Campaign,
contentDescription = null,
tint = MaterialTheme.colors.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = title,
style = MaterialTheme.typography.subtitle1,
)
Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
contentDescription = null,
tint = MaterialTheme.colors.primary
)
}

AnimatedVisibility(visible = isExpanded) {
Text(
text = message,
style = MaterialTheme.typography.body2,
color = LocalContentColor.current.copy(alpha = ContentAlpha.medium),
modifier = Modifier.padding(start = 48.dp, end = 16.dp)
)
}

Row(
modifier = Modifier
.align(Alignment.End)
.padding(horizontal = 16.dp)
) {
buttons()
}
}
}
}

@Composable
fun ExpandableTopBanner(
title: String,
message: String,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
expandedByDefault: Boolean = false,
) {
ExpandableTopBanner(
title = title,
message = message,
buttons = {
WCTextButton(
onClick = onDismiss,
) {
Text(stringResource(id = R.string.dismiss))
}
},
modifier = modifier,
expandedByDefault = expandedByDefault,
)
}

@Composable
@LightDarkThemePreviews
private fun ExpandableTopBannerPreview() {
WooThemeWithBackground {
ExpandableTopBanner(
title = "Title",
message = "Message",
onDismiss = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.woocommerce.android.ui.customfields.list

import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
Expand Down Expand Up @@ -35,6 +36,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.SpanStyle
Expand All @@ -46,6 +48,7 @@ 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.DiscardChangesDialog
import com.woocommerce.android.ui.compose.component.ExpandableTopBanner
import com.woocommerce.android.ui.compose.component.ProgressDialog
import com.woocommerce.android.ui.compose.component.Toolbar
import com.woocommerce.android.ui.compose.component.WCTextButton
Expand Down Expand Up @@ -120,28 +123,42 @@ private fun CustomFieldsScreen(
) { paddingValues ->
val pullToRefreshState = rememberPullRefreshState(state.isRefreshing, onPullToRefresh)

Box(
Column(
modifier = Modifier
.padding(paddingValues)
.pullRefresh(state = pullToRefreshState)
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(state.customFields) { customField ->
CustomFieldItem(
customField = customField,
onClicked = onCustomFieldClicked,
onValueClicked = onCustomFieldValueClicked,
modifier = Modifier.fillMaxWidth()
)
Divider()
}
if (state.topBannerState != null) {
ExpandableTopBanner(
title = stringResource(id = R.string.custom_fields_list_top_banner_title),
message = stringResource(id = R.string.custom_fields_list_top_banner_message),
onDismiss = state.topBannerState.onDismiss,
expandedByDefault = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT,
modifier = Modifier.fillMaxWidth()
)
}
Box(
modifier = Modifier
.padding(paddingValues)
.pullRefresh(state = pullToRefreshState)
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(state.customFields) { customField ->
CustomFieldItem(
customField = customField,
onClicked = onCustomFieldClicked,
onValueClicked = onCustomFieldValueClicked,
modifier = Modifier.fillMaxWidth()
)
Divider()
}
}

PullRefreshIndicator(
refreshing = state.isRefreshing,
state = pullToRefreshState,
modifier = Modifier.align(Alignment.TopCenter)
)
PullRefreshIndicator(
refreshing = state.isRefreshing,
state = pullToRefreshState,
modifier = Modifier.align(Alignment.TopCenter)
)
}
}

if (state.isSaving) {
Expand Down Expand Up @@ -245,7 +262,8 @@ private fun CustomFieldsScreenPreview() {
)
),
CustomFieldUiModel(CustomField(3, "key4", "https://url.com")),
)
),
topBannerState = CustomFieldsViewModel.TopBannerState { }
),
onPullToRefresh = {},
onSaveClicked = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.woocommerce.android.AppPrefsWrapper
import com.woocommerce.android.R
import com.woocommerce.android.extensions.combine
import com.woocommerce.android.ui.customfields.CustomFieldUiModel
import com.woocommerce.android.ui.customfields.CustomFieldsRepository
import com.woocommerce.android.viewmodel.MultiLiveEvent
Expand All @@ -14,7 +16,9 @@ import com.woocommerce.android.viewmodel.getStateFlow
import com.woocommerce.android.viewmodel.navArgs
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
Expand All @@ -25,6 +29,7 @@ import javax.inject.Inject
class CustomFieldsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val repository: CustomFieldsRepository,
private val appPrefs: AppPrefsWrapper,
private val resourceProvider: ResourceProvider
) : ScopedViewModel(savedStateHandle) {
private val args: CustomFieldsFragmentArgs by savedStateHandle.navArgs()
Expand All @@ -39,14 +44,19 @@ class CustomFieldsViewModel @Inject constructor(
)
private val customFields = repository.observeDisplayableCustomFields(args.parentItemId)
private val pendingChanges = savedStateHandle.getStateFlow(viewModelScope, PendingChanges())
private val bannerDismissed = appPrefs.observePrefs()
.onStart { emit(Unit) }
.map { appPrefs.isCustomFieldsTopBannerDismissed }
.distinctUntilChanged()

val state = combine(
customFields,
pendingChanges,
isRefreshing,
isSaving,
showDiscardChangesDialog
) { customFields, pendingChanges, isLoading, isSaving, isShowingDiscardDialog ->
showDiscardChangesDialog,
bannerDismissed
) { customFields, pendingChanges, isLoading, isSaving, isShowingDiscardDialog, bannerDismissed ->
UiState(
customFields = customFields.map { CustomFieldUiModel(it) }.combineWithChanges(pendingChanges),
isRefreshing = isLoading,
Expand All @@ -57,6 +67,11 @@ class CustomFieldsViewModel @Inject constructor(
onDiscard = { triggerEvent(MultiLiveEvent.Event.Exit) },
onCancel = { showDiscardChangesDialog.value = false }
)
},
topBannerState = bannerDismissed.takeIf { !it }?.let {
TopBannerState {
appPrefs.isCustomFieldsTopBannerDismissed = true
}
}
)
}.asLiveData()
Expand Down Expand Up @@ -173,14 +188,19 @@ class CustomFieldsViewModel @Inject constructor(
val isRefreshing: Boolean = false,
val isSaving: Boolean = false,
val hasChanges: Boolean = false,
val discardChangesDialogState: DiscardChangesDialogState? = null
val discardChangesDialogState: DiscardChangesDialogState? = null,
val topBannerState: TopBannerState? = null
)

data class DiscardChangesDialogState(
val onDiscard: () -> Unit,
val onCancel: () -> Unit
)

data class TopBannerState(
val onDismiss: () -> Unit
)

@Parcelize
private data class PendingChanges(
val editedFields: List<CustomFieldUiModel> = emptyList(),
Expand Down
2 changes: 2 additions & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4296,6 +4296,8 @@
<string name="custom_fields_list_saving_succeeded">Changes saved</string>
<string name="custom_fields_list_saving_failed">Saving changes failed, please try again</string>
<string name="custom_fields_list_field_deleted">Custom Field deleted</string>
<string name="custom_fields_list_top_banner_title">View and edit Custom Fields</string>
<string name="custom_fields_list_top_banner_message">When saving changes to custom fields, they will take effect immediately.</string>
<string name="custom_fields_add_button">Add custom fields</string>
<string name="custom_fields_editor_key_label">Key</string>
<string name="custom_fields_editor_value_label">Value</string>
Expand Down
Loading

0 comments on commit cdf6e5b

Please sign in to comment.