Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/12597 custom fields banner #12601

Merged
merged 7 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading