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

[Woo POS][Non-Simple Products] Handle empty state on variation screen #13209

Merged
merged 17 commits into from
Jan 13, 2025
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 @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.products.variations.selector

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.wordpress.android.fluxc.store.WCProductStore
import javax.inject.Inject

class VariationListHandler @Inject constructor(private val repository: VariationSelectorRepository) {
Expand All @@ -26,24 +27,39 @@ class VariationListHandler @Inject constructor(private val repository: Variation
return canLoadMore || (offset + PAGE_SIZE < numOfVariations)
}

suspend fun fetchVariations(productId: Long, forceRefresh: Boolean = false): Result<Unit> = mutex.withLock {
suspend fun fetchVariations(
productId: Long,
forceRefresh: Boolean = false,
filterOptions: Map<WCProductStore.VariationFilterOption, String>? = null
): Result<Unit> = mutex.withLock {
// Reset the offset
offset = 0

if (forceRefresh) {
loadVariations(productId)
loadVariations(productId, filterOptions)
} else {
Result.success(Unit)
}
}

suspend fun loadMore(productId: Long): Result<Unit> = mutex.withLock {
suspend fun loadMore(
productId: Long,
filterOptions: Map<WCProductStore.VariationFilterOption, String>? = null
): Result<Unit> = mutex.withLock {
if (!canLoadMore) return@withLock Result.success(Unit)
loadVariations(productId)
loadVariations(productId, filterOptions)
}

private suspend fun loadVariations(productId: Long): Result<Unit> {
return repository.fetchVariations(productId, offset, PAGE_SIZE).onSuccess {
private suspend fun loadVariations(
productId: Long,
filterOptions: Map<WCProductStore.VariationFilterOption, String>? = null
): Result<Unit> {
return repository.fetchVariations(
productId,
offset,
PAGE_SIZE,
filterOptions
).onSuccess {
canLoadMore = it
offset += PAGE_SIZE
}.map { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,16 @@ class VariationSelectorRepository @Inject constructor(
suspend fun fetchVariations(
productId: Long,
offset: Int,
pageSize: Int
pageSize: Int,
filterOptions: Map<WCProductStore.VariationFilterOption, String>? = null
): Result<Boolean> {
return productStore.fetchProductVariations(selectedSite.get(), productId, offset, pageSize)
return productStore.fetchProductVariations(
selectedSite.get(),
productId,
offset,
pageSize,
filterOptions = filterOptions,
)
.let { result ->
if (result.isError) {
WooLog.w(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.woocommerce.android.ui.woopos.home.items

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -10,14 +11,17 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
Expand All @@ -29,12 +33,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
Expand All @@ -45,6 +52,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.WooPosCard
import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme
import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosLazyColumn
import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox
import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding
import com.woocommerce.android.ui.woopos.home.items.WooPosItem.SimpleProduct
import com.woocommerce.android.ui.woopos.home.items.WooPosItem.VariableProduct
import com.woocommerce.android.ui.woopos.home.items.WooPosItem.Variation
Expand Down Expand Up @@ -325,6 +333,51 @@ fun ItemsLoadingItem() {
}
}

@Composable
fun ItemsEmptyList(
title: String,
message: String,
contentDescription: String,
) {
Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
modifier = Modifier.size(104.dp),
imageVector = ImageVector.vectorResource(id = R.drawable.ic_woo_pos_empty_products),
contentDescription = contentDescription,
)

Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding()))

Text(
text = title,
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
)

Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding()))

Text(
text = message,
style = MaterialTheme.typography.h5,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
textAlign = TextAlign.Center
)

Spacer(modifier = Modifier.height(8.dp.toAdaptivePadding()))
}
}
}

@Composable
private fun InfiniteListHandler(
listState: LazyListState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.woocommerce.android.ui.woopos.home.items

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand All @@ -17,8 +16,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ContentAlpha
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
Expand All @@ -35,12 +32,9 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.woocommerce.android.R
Expand Down Expand Up @@ -182,7 +176,11 @@ private fun MainItemsList(

is WooPosItemsViewState.Loading -> ItemsLoadingIndicator()

is WooPosItemsViewState.Empty -> ProductsEmptyList()
is WooPosItemsViewState.Empty -> ItemsEmptyList(
title = stringResource(id = R.string.woopos_products_empty_list_title),
message = stringResource(id = R.string.woopos_products_empty_list_message),
contentDescription = stringResource(id = R.string.woopos_products_empty_list_image_description),
)

is WooPosItemsViewState.Error -> ProductsError { onRetryClicked() }
}
Expand Down Expand Up @@ -265,47 +263,6 @@ private fun SimpleProductsBanner(
}
}

@Composable
fun ProductsEmptyList() {
Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
modifier = Modifier.size(104.dp),
imageVector = ImageVector.vectorResource(id = R.drawable.ic_woo_pos_empty_products),
contentDescription = stringResource(id = R.string.woopos_products_empty_list_image_description),
)

Spacer(modifier = Modifier.height(40.dp.toAdaptivePadding()))

Text(
text = stringResource(id = R.string.woopos_products_empty_list_title),
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
)

Spacer(modifier = Modifier.height(16.dp.toAdaptivePadding()))

Text(
text = stringResource(id = R.string.woopos_products_empty_list_message),
style = MaterialTheme.typography.h5,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
textAlign = TextAlign.Center
)

Spacer(modifier = Modifier.height(8.dp.toAdaptivePadding()))
}
}
}

@Composable
fun ProductsError(onRetryClicked: () -> Unit) {
Box(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.home.items.products

import com.woocommerce.android.model.Product
import com.woocommerce.android.ui.products.ProductStatus
import com.woocommerce.android.ui.products.ProductType.VARIABLE
import com.woocommerce.android.ui.products.selector.ProductListHandler
import com.woocommerce.android.util.WooLog
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -83,7 +84,7 @@ class WooPosProductsDataSource @Inject constructor(
}

private fun List<Product>.applyPosProductFilter() = this.filter { product ->
isProductHasAPrice(product)
isProductHasAPrice(product) || product.productType == VARIABLE
}

private fun isProductHasAPrice(product: Product) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import org.wordpress.android.fluxc.store.WCProductStore
import javax.inject.Inject
import javax.inject.Singleton

Expand Down Expand Up @@ -47,7 +48,13 @@ class WooPosVariationsDataSource @Inject constructor(
emit(FetchResult.Cached(cachedVariations))
}

val result = handler.fetchVariations(productId, forceRefresh = true)
val result = handler.fetchVariations(
productId,
forceRefresh = true,
filterOptions = mapOf(
WCProductStore.VariationFilterOption.STATUS to "publish"
)
)
if (result.isSuccess) {
val remoteVariations = handler.getVariationsFlow(productId).firstOrNull()?.applyFilter() ?: emptyList()
updateCache(productId, remoteVariations)
Expand All @@ -64,7 +71,12 @@ class WooPosVariationsDataSource @Inject constructor(
}.flowOn(Dispatchers.IO)

suspend fun loadMore(productId: Long): Result<List<ProductVariation>> = withContext(Dispatchers.IO) {
val result = handler.loadMore(productId)
val result = handler.loadMore(
productId,
filterOptions = mapOf(
WCProductStore.VariationFilterOption.STATUS to VARIATION_STATUS_PUBLISH
)
)
if (result.isSuccess) {
val fetchedVariations = handler.getVariationsFlow(productId).first().applyFilter()
Result.success(fetchedVariations)
Expand All @@ -75,6 +87,10 @@ class WooPosVariationsDataSource @Inject constructor(
)
}
}

companion object {
private const val VARIATION_STATUS_PUBLISH = "publish"
}
}

private fun Result<Unit>.logFailure() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.component.Button
import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosErrorScreen
import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosPaginationErrorIndicator
import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding
import com.woocommerce.android.ui.woopos.home.items.ItemsEmptyList
import com.woocommerce.android.ui.woopos.home.items.ItemsLoadingIndicator
import com.woocommerce.android.ui.woopos.home.items.WooPosItem
import com.woocommerce.android.ui.woopos.home.items.WooPosItemList
Expand Down Expand Up @@ -157,7 +158,15 @@ private fun WooPosVariationsScreens(
}
}

else -> {}
is WooPosVariationsViewState.Empty -> {
ItemsEmptyList(
title = stringResource(id = R.string.woopos_variations_empty_list_title),
message = stringResource(id = R.string.woopos_variations_empty_list_message),
contentDescription = stringResource(
id = R.string.woopos_variations_empty_list_image_description
)
)
}
}
}
PullRefreshIndicator(
Expand Down
6 changes: 5 additions & 1 deletion WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4321,7 +4321,7 @@
<string name="woopos_cart_empty_content_description">Cart is empty</string>
<string name="woopos_products_empty_list_image_description">No products</string>
<string name="woopos_products_empty_list_title">No supported products found</string>
<string name="woopos_products_empty_list_message">POS currently only supports simple and variable products – \ncreate one to get started.</string>
<string name="woopos_products_empty_list_message">POS currently only supports simple, variable, and virtual products – \ncreate one to get started.</string>
<string name="woopos_products_empty_list_message_two">POS currently only supports simple products</string>
<string name="woopos_products_loading_error_title">Error loading products</string>
<string name="woopos_products_loading_error_message">Give it another go?</string>
Expand All @@ -4331,6 +4331,10 @@
<string name="woopos_totals_success_payment_cash">A cash payment of %1$s was successfully made</string>
<string name="woopos_totals_success_payment_card">A card payment of %1$s was successfully made</string>

<string name="woopos_variations_empty_list_title">No supported variations found</string>
<string name="woopos_variations_empty_list_message">POS currently only supports simple, variable, and virtual products – \ncreate one to get started.</string>
<string name="woopos_variations_empty_list_image_description">No variations</string>

<string name="woopos_success_totals_error_reader_not_connected_title">Reader not connected</string>
<string name="woopos_success_totals_error_reader_not_connected_subtitle">To process this payment, please connect your reader.</string>
<string name="woopos_success_totals_error_reader_not_connected_cta_button_label">Connect to reader</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ object ProductTestUtils {
amount: String = "10.00",
isVirtual: Boolean = false,
isDownloadable: Boolean = false,
isPurchasable: Boolean = true,
): ProductVariation {
return WCProductVariationModel(2).apply {
dateCreated = "2018-01-05T05:14:30Z"
Expand All @@ -131,6 +132,7 @@ object ProductTestUtils {
attributes = ""
virtual = isVirtual
downloadable = isDownloadable
purchasable = isPurchasable
}.toAppModel().also { it.priceWithCurrency = "$10.00" }
}

Expand Down
Loading
Loading