From c3ef48f433942169edaa5aef603e940513c71621 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 10:33:11 +0530 Subject: [PATCH 01/80] Add WooPosBaseViewState that acts as a parent for all items view state --- .../ui/woopos/home/items/WooPosBaseViewState.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosBaseViewState.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosBaseViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosBaseViewState.kt new file mode 100644 index 00000000000..5e499db2a43 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosBaseViewState.kt @@ -0,0 +1,11 @@ +package com.woocommerce.android.ui.woopos.home.items + +sealed class WooPosBaseViewState( + open val reloadingProductsWithPullToRefresh: Boolean +) + +interface ContentViewState { + val items: List + val loadingMore: Boolean + val reloadingProductsWithPullToRefresh: Boolean +} From ef718d0ec9c31dbba669d058ebf35171e4d69a4e Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 10:34:14 +0530 Subject: [PATCH 02/80] Refactor WooPosItemsViewState to incorporate changes from WooPosBaseViewState --- .../woopos/home/items/WooPosItemsViewState.kt | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewState.kt index f7398780ad2..f84e7107a9e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewState.kt @@ -4,31 +4,32 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes sealed class WooPosItemsViewState( - open val reloadingProductsWithPullToRefresh: Boolean, -) { + override val reloadingProductsWithPullToRefresh: Boolean, +) : WooPosBaseViewState(reloadingProductsWithPullToRefresh) { data class Content( - val items: List, - val loadingMore: Boolean, + override val items: List, + override val loadingMore: Boolean, val bannerState: BannerState, - override val reloadingProductsWithPullToRefresh: Boolean = false, - ) : WooPosItemsViewState(reloadingProductsWithPullToRefresh) { + override val reloadingProductsWithPullToRefresh: Boolean = false + ) : WooPosItemsViewState(reloadingProductsWithPullToRefresh), ContentViewState { data class BannerState( val isBannerHiddenByUser: Boolean, @StringRes val title: Int, @StringRes val message: Int, - @DrawableRes val icon: Int, + @DrawableRes val icon: Int ) } data class Loading( override val reloadingProductsWithPullToRefresh: Boolean = false, - val withCart: Boolean, - ) : - WooPosItemsViewState(reloadingProductsWithPullToRefresh) + val withCart: Boolean + ) : WooPosItemsViewState(reloadingProductsWithPullToRefresh) - data class Error(override val reloadingProductsWithPullToRefresh: Boolean = false) : - WooPosItemsViewState(reloadingProductsWithPullToRefresh) + data class Error( + override val reloadingProductsWithPullToRefresh: Boolean = false + ) : WooPosItemsViewState(reloadingProductsWithPullToRefresh) - data class Empty(override val reloadingProductsWithPullToRefresh: Boolean = false) : - WooPosItemsViewState(reloadingProductsWithPullToRefresh) + data class Empty( + override val reloadingProductsWithPullToRefresh: Boolean = false + ) : WooPosItemsViewState(reloadingProductsWithPullToRefresh) } From 383559036faa74a321c1a0e0018f1183f075a9cd Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 10:34:32 +0530 Subject: [PATCH 03/80] Add WooPosVariationsViewState --- .../home/items/WooPosVariationsViewState.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosVariationsViewState.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosVariationsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosVariationsViewState.kt new file mode 100644 index 00000000000..5f3be32c26b --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosVariationsViewState.kt @@ -0,0 +1,25 @@ +package com.woocommerce.android.ui.woopos.home.items + +sealed class WooPosVariationsViewState( + override val reloadingProductsWithPullToRefresh: Boolean +) : WooPosBaseViewState(reloadingProductsWithPullToRefresh) { + + data class Content( + override val items: List, + override val loadingMore: Boolean, + override val reloadingProductsWithPullToRefresh: Boolean = false + ) : WooPosVariationsViewState(reloadingProductsWithPullToRefresh), ContentViewState + + data class Loading( + override val reloadingProductsWithPullToRefresh: Boolean = false, + val withCart: Boolean + ) : WooPosVariationsViewState(reloadingProductsWithPullToRefresh) + + data class Error( + override val reloadingProductsWithPullToRefresh: Boolean = false + ) : WooPosVariationsViewState(reloadingProductsWithPullToRefresh) + + data class Empty( + override val reloadingProductsWithPullToRefresh: Boolean = false + ) : WooPosVariationsViewState(reloadingProductsWithPullToRefresh) +} From 18d1fcbcebe4949abcbe37da6267b52e8e6ab88e Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 10:34:46 +0530 Subject: [PATCH 04/80] Add Variation model --- .../android/ui/woopos/home/items/WooPosItem.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItem.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItem.kt index 73ac51f02ca..31afbc84d52 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItem.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItem.kt @@ -36,6 +36,17 @@ sealed class WooPosItem( ) } } + + data class Variation( + override val id: Long, + override val name: String, + val price: String, + val imageUrl: String?, + ) : WooPosItem(id, name), ClickableItem { + override fun onItemClick(onUIEvent: (WooPosItemsUIEvent) -> Unit) { + onUIEvent(WooPosItemsUIEvent.ItemClicked(this)) + } + } } interface ClickableItem { From 3b19d3728e42de5a856112a76bdac0692c1d571c Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 10:35:23 +0530 Subject: [PATCH 05/80] Extract ItemsList UI into a separate class so that it can be reused for all items list screens like products, variations ...etc --- .../ui/woopos/home/items/WooPosItemsList.kt | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt new file mode 100644 index 00000000000..89f6e85455d --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt @@ -0,0 +1,317 @@ +package com.woocommerce.android.ui.woopos.home.items + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.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.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow +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.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +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.TextOverflow +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.woocommerce.android.R +import com.woocommerce.android.ui.woopos.common.composeui.ShadowType +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.home.items.WooPosItem.SimpleProduct +import com.woocommerce.android.ui.woopos.home.items.WooPosItem.VariableProduct +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun ItemsList( + state: ContentViewState, + onItemClicked: (item: WooPosItem) -> Unit, + onEndOfProductsListReached: () -> Unit, +) { + val listState = rememberLazyListState() + WooPosLazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(2.dp), + state = listState, + ) { + items( + state.items, + key = { product -> product.id } + ) { product -> + when (product) { + is SimpleProduct -> { + ProductItem( + modifier = Modifier.animateItemPlacement(), + item = product, + onItemClicked = onItemClicked + ) + } + + is VariableProduct -> { + VariableProductItem( + modifier = Modifier.animateItemPlacement(), + item = product, + onItemClicked = onItemClicked + ) + } + + is WooPosItem.Variation -> { + VariationItem( + modifier = Modifier.animateItemPlacement(), + item = product, + onItemClicked = onItemClicked + ) + } + } + } + + if (state.loadingMore) { + item { + ItemsLoadingItem() + } + } + item { + Spacer(modifier = Modifier.height(104.dp)) + } + } + InfiniteListHandler(listState, state) { + onEndOfProductsListReached() + } +} + +@Composable +private fun ProductItem( + modifier: Modifier = Modifier, + item: SimpleProduct, + onItemClicked: (item: WooPosItem) -> Unit +) { + val itemContentDescription = stringResource( + id = R.string.woopos_cart_item_content_description, + item.name, + item.price + ) + ItemCard(modifier, itemContentDescription, onItemClicked, item) +} + +@Composable +private fun VariableProductItem( + modifier: Modifier = Modifier, + item: VariableProduct, + onItemClicked: (item: WooPosItem) -> Unit +) { + val itemContentDescription = stringResource( + id = R.string.woopos_cart_item_content_description, + item.name, + item.price + ) + ItemCard(modifier, itemContentDescription, onItemClicked, item) +} + +@Composable +private fun VariationItem( + modifier: Modifier = Modifier, + item: WooPosItem.Variation, + onItemClicked: (item: WooPosItem) -> Unit +) { + val itemContentDescription = stringResource( + id = R.string.woopos_cart_item_content_description, + item.name, + item.price + ) + ItemCard(modifier, itemContentDescription, onItemClicked, item) +} + +@Composable +private fun ItemCard( + modifier: Modifier, + itemContentDescription: String, + onItemClicked: (item: WooPosItem) -> Unit, + item: WooPosItem +) { + WooPosCard( + modifier = modifier + .semantics { contentDescription = itemContentDescription }, + shape = RoundedCornerShape(8.dp), + backgroundColor = MaterialTheme.colors.surface, + elevation = 6.dp, + shadowType = ShadowType.Soft, + ) { + Row( + modifier = Modifier + .clickable { onItemClicked(item) } + .height(112.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + ProductImage(item) + + Spacer(modifier = Modifier.width(32.dp)) + + ProductInfo(item) + } + } +} + +@Composable +private fun ProductInfo(item: WooPosItem) { + Column( + modifier = Modifier + .fillMaxHeight() + .padding(vertical = 8.dp), + verticalArrangement = Arrangement.Center + ) { + Text( + text = item.name, + style = MaterialTheme.typography.h5, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(8.dp)) + when (item) { + is SimpleProduct -> SimpleProductDetails(item = item) + is VariableProduct -> VariableProductDetails(item = item) + is WooPosItem.Variation -> {} + } + } +} + +@Composable +private fun ProductImage(item: WooPosItem) { + val imageUrl = when (item) { + is SimpleProduct -> item.imageUrl + is VariableProduct -> item.imageUrl + is WooPosItem.Variation -> {""} + } + + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(imageUrl) + .crossfade(true) + .build(), + fallback = ColorPainter(WooPosTheme.colors.loadingSkeleton), + error = ColorPainter(WooPosTheme.colors.loadingSkeleton), + placeholder = ColorPainter(WooPosTheme.colors.loadingSkeleton), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.size(112.dp) + ) +} + +@Composable +private fun SimpleProductDetails(item: SimpleProduct) { + Text( + text = item.price, + style = MaterialTheme.typography.h6, + fontWeight = FontWeight.Normal + ) +} + +@Composable +private fun VariableProductDetails(item: VariableProduct) { + Text( + text = "${item.numOfVariations} Variations", + style = MaterialTheme.typography.h6, + fontWeight = FontWeight.Normal + ) +} + +@Composable +fun ItemsLoadingItem() { + WooPosCard( + shape = RoundedCornerShape(8.dp), + backgroundColor = MaterialTheme.colors.surface, + elevation = 6.dp, + shadowType = ShadowType.Soft, + ) { + Row( + modifier = Modifier + .height(112.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(112.dp) + .background(WooPosTheme.colors.loadingSkeleton) + ) + + Spacer(modifier = Modifier.width(32.dp)) + + WooPosShimmerBox( + modifier = Modifier + .weight(1f) + .height(30.dp) + .clip(RoundedCornerShape(4.dp)) + ) + + Spacer(modifier = Modifier.width(184.dp)) + + WooPosShimmerBox( + modifier = Modifier + .height(30.dp) + .width(76.dp) + .clip(RoundedCornerShape(4.dp)) + ) + + Spacer(modifier = Modifier.width(24.dp)) + } + } +} + +@Composable +private fun InfiniteListHandler( + listState: LazyListState, + state: ContentViewState, + onEndOfProductsListReached: () -> Unit +) { + val buffer = 5 + val loadMore = remember { + derivedStateOf { + val layoutInfo = listState.layoutInfo + val totalItemsNumber = layoutInfo.totalItemsCount + val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1 + + lastVisibleItemIndex > (totalItemsNumber - buffer) + } + } + + LaunchedEffect(state.reloadingProductsWithPullToRefresh) { + snapshotFlow { loadMore.value } + .distinctUntilChanged() + .filter { it } + .collect { + onEndOfProductsListReached() + } + } +} From 35b91c85295314b7cffaac82ba305da23a49feba Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 10:35:40 +0530 Subject: [PATCH 06/80] Extract ItemsList UI into a separate class so that it can be reused for all items list screens like products, variations ...etc --- .../ui/woopos/home/items/WooPosItemsScreen.kt | 266 ------------------ 1 file changed, 266 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt index 9f9a6d80fb5..3b3cc89b148 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt @@ -3,10 +3,7 @@ package com.woocommerce.android.ui.woopos.home.items import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade import androidx.compose.animation.shrinkVertically -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 import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -20,11 +17,7 @@ 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.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.ContentAlpha import androidx.compose.material.ExperimentalMaterialApi @@ -37,41 +30,25 @@ import androidx.compose.material.pullrefresh.PullRefreshState import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -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.painterResource 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 androidx.hilt.navigation.compose.hiltViewModel -import coil.compose.AsyncImage -import coil.request.ImageRequest import com.woocommerce.android.R -import com.woocommerce.android.ui.woopos.common.composeui.ShadowType -import com.woocommerce.android.ui.woopos.common.composeui.WooPosCard import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme 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.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 @@ -82,8 +59,6 @@ import com.woocommerce.android.ui.woopos.home.items.WooPosItemsUIEvent.PullToRef import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsScreen import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter @OptIn(ExperimentalMaterialApi::class) @Composable @@ -316,56 +291,6 @@ private fun SimpleProductsBanner( } } -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun ItemsList( - state: WooPosItemsViewState.Content, - onItemClicked: (item: WooPosItem) -> Unit, - onEndOfProductsListReached: () -> Unit, -) { - val listState = rememberLazyListState() - WooPosLazyColumn( - verticalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(2.dp), - state = listState, - ) { - items( - state.items, - key = { product -> product.id } - ) { product -> - when (product) { - is SimpleProduct -> { - ProductItem( - modifier = Modifier.animateItemPlacement(), - item = product, - onItemClicked = onItemClicked - ) - } - - is VariableProduct -> { - VariableProductItem( - modifier = Modifier.animateItemPlacement(), - item = product, - onItemClicked = onItemClicked - ) - } - } - } - - if (state.loadingMore) { - item { - ItemsLoadingItem() - } - } - item { - Spacer(modifier = Modifier.height(104.dp)) - } - } - InfiniteListHandler(listState, state) { - onEndOfProductsListReached() - } -} - @Composable fun ItemsLoadingIndicator() { WooPosLazyColumn( @@ -382,170 +307,6 @@ fun ItemsLoadingIndicator() { } } -@Composable -private fun ItemsLoadingItem() { - WooPosCard( - shape = RoundedCornerShape(8.dp), - backgroundColor = MaterialTheme.colors.surface, - elevation = 6.dp, - shadowType = ShadowType.Soft, - ) { - Row( - modifier = Modifier - .height(112.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .size(112.dp) - .background(WooPosTheme.colors.loadingSkeleton) - ) - - Spacer(modifier = Modifier.width(32.dp)) - - WooPosShimmerBox( - modifier = Modifier - .weight(1f) - .height(30.dp) - .clip(RoundedCornerShape(4.dp)) - ) - - Spacer(modifier = Modifier.width(184.dp)) - - WooPosShimmerBox( - modifier = Modifier - .height(30.dp) - .width(76.dp) - .clip(RoundedCornerShape(4.dp)) - ) - - Spacer(modifier = Modifier.width(24.dp)) - } - } -} - -@Composable -private fun ProductItem( - modifier: Modifier = Modifier, - item: SimpleProduct, - onItemClicked: (item: WooPosItem) -> Unit -) { - val itemContentDescription = stringResource( - id = R.string.woopos_cart_item_content_description, - item.name, - item.price - ) - ItemCard(modifier, itemContentDescription, onItemClicked, item) -} - -@Composable -private fun VariableProductItem( - modifier: Modifier = Modifier, - item: VariableProduct, - onItemClicked: (item: WooPosItem) -> Unit -) { - val itemContentDescription = stringResource( - id = R.string.woopos_cart_item_content_description, - item.name, - item.price - ) - ItemCard(modifier, itemContentDescription, onItemClicked, item) -} - -@Composable -private fun ItemCard( - modifier: Modifier, - itemContentDescription: String, - onItemClicked: (item: WooPosItem) -> Unit, - item: WooPosItem -) { - WooPosCard( - modifier = modifier - .semantics { contentDescription = itemContentDescription }, - shape = RoundedCornerShape(8.dp), - backgroundColor = MaterialTheme.colors.surface, - elevation = 6.dp, - shadowType = ShadowType.Soft, - ) { - Row( - modifier = Modifier - .clickable { onItemClicked(item) } - .height(112.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - ProductImage(item) - - Spacer(modifier = Modifier.width(32.dp)) - - ProductInfo(item) - } - } -} - -@Composable -private fun ProductInfo(item: WooPosItem) { - Column( - modifier = Modifier - .fillMaxHeight() - .padding(vertical = 8.dp), - verticalArrangement = Arrangement.Center - ) { - Text( - text = item.name, - style = MaterialTheme.typography.h5, - fontWeight = FontWeight.SemiBold, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = Modifier.height(8.dp)) - when (item) { - is SimpleProduct -> SimpleProductDetails(item = item) - is VariableProduct -> VariableProductDetails(item = item) - } - } -} - -@Composable -private fun ProductImage(item: WooPosItem) { - val imageUrl = when (item) { - is SimpleProduct -> item.imageUrl - is VariableProduct -> item.imageUrl - } - - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(imageUrl) - .crossfade(true) - .build(), - fallback = ColorPainter(WooPosTheme.colors.loadingSkeleton), - error = ColorPainter(WooPosTheme.colors.loadingSkeleton), - placeholder = ColorPainter(WooPosTheme.colors.loadingSkeleton), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier.size(112.dp) - ) -} - -@Composable -private fun SimpleProductDetails(item: SimpleProduct) { - Text( - text = item.price, - style = MaterialTheme.typography.h6, - fontWeight = FontWeight.Normal - ) -} - -@Composable -private fun VariableProductDetails(item: VariableProduct) { - Text( - text = "${item.numOfVariations} Variations", - style = MaterialTheme.typography.h6, - fontWeight = FontWeight.Normal - ) -} - @Composable fun ProductsEmptyList() { Box( @@ -605,33 +366,6 @@ fun ProductsError(onRetryClicked: () -> Unit) { } } -@Composable -private fun InfiniteListHandler( - listState: LazyListState, - state: WooPosItemsViewState.Content, - onEndOfProductsListReached: () -> Unit -) { - val buffer = 5 - val loadMore = remember { - derivedStateOf { - val layoutInfo = listState.layoutInfo - val totalItemsNumber = layoutInfo.totalItemsCount - val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1 - - lastVisibleItemIndex > (totalItemsNumber - buffer) - } - } - - LaunchedEffect(state.reloadingProductsWithPullToRefresh) { - snapshotFlow { loadMore.value } - .distinctUntilChanged() - .filter { it } - .collect { - onEndOfProductsListReached() - } - } -} - @OptIn(ExperimentalMaterialApi::class) @Composable @WooPosPreview From 1d855cd56da1cc94c4f709055d9ec92fce251d43 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 10:36:09 +0530 Subject: [PATCH 07/80] Add a dummy click handler for variations. --- .../android/ui/woopos/home/items/WooPosItemsViewModel.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewModel.kt index 5675b5c4031..9ae6f885b6d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewModel.kt @@ -113,6 +113,14 @@ class WooPosItemsViewModel @Inject constructor( is VariableProduct -> { } + + is WooPosItem.Variation -> { + onItemClicked( + WooPosItemNavigationData.SimpleProductData( + id = event.item.id + ) + ) + } } } From b38275729b235a3b5c9da895f5c3fdeb37068b9d Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 10:36:34 +0530 Subject: [PATCH 08/80] Add variations screen and view model --- .../variations/WooPosVariationsScreen.kt | 102 +++++++++++++++--- .../variations/WooPosVariationsViewModel.kt | 87 +++++++++++++++ 2 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index 9c074586cac..afc5b5bc93e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -3,9 +3,11 @@ package com.woocommerce.android.ui.woopos.home.items.variations import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +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.material.Icon import androidx.compose.material.IconButton @@ -14,14 +16,25 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout +import androidx.hilt.navigation.compose.hiltViewModel +import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding +import com.woocommerce.android.ui.woopos.home.items.ItemsList +import com.woocommerce.android.ui.woopos.home.items.WooPosItem +import com.woocommerce.android.ui.woopos.home.items.WooPosItem.SimpleProduct import com.woocommerce.android.ui.woopos.home.items.WooPosItemNavigationData.VariableProductData +import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState +import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow @Composable fun WooPosVariationsScreen( @@ -29,6 +42,23 @@ fun WooPosVariationsScreen( variableProductData: VariableProductData, onBackClicked: () -> Unit ) { + val viewModel: WooPosVariationsViewModel = hiltViewModel() + LaunchedEffect(variableProductData.id) { + viewModel.init(variableProductData.id) + } + val state = viewModel.viewState + WooPosVariationsScreens(modifier, viewModel, onBackClicked, variableProductData, state) +} + +@Composable +private fun WooPosVariationsScreens( + modifier: Modifier, + viewModel: WooPosVariationsViewModel, + onBackClicked: () -> Unit, + variableProductData: VariableProductData, + state: StateFlow +) { + val itemState = state.collectAsState() Box( modifier = modifier .fillMaxSize() @@ -47,6 +77,16 @@ fun WooPosVariationsScreen( variableProductData = variableProductData, onBackClicked = onBackClicked ) + when (val itemsState = itemState.value) { + is WooPosVariationsViewState.Content -> { + Spacer(modifier = Modifier.height(16.dp)) + ItemsList(state = itemsState, onItemClicked = {}) { + viewModel.loadMore(variableProductData.id) + } + } + + else -> {} + } } } } @@ -101,19 +141,49 @@ private fun VariationsToolbar( } } -@Composable -@WooPosPreview -fun WooPosVariationsScreenPreview() { - WooPosTheme { - WooPosVariationsScreen( - modifier = Modifier, - variableProductData = VariableProductData( - id = 0, - name = "Variable Product", - numOfVariations = 20, - variationIds = emptyList() - ) - ) { - } - } -} +//@Composable +//@WooPosPreview +//fun WooPosVariationsScreenPreview() { +// val productState = MutableStateFlow( +// WooPosVariationsViewState.Content( +// items = listOf( +// WooPosItem.Variation( +// 1, +// name = "Product 1, Product 1, Product 1, " + +// "Product 1, Product 1, Product 1, Product 1, Product 1" + +// "Product 1, Product 1, Product 1, Product 1, Product 1", +// price = "10.0$", +// imageUrl = null, +// ), +// WooPosItem.Variation( +// 2, +// name = "Product 2", +// price = "2000.00$", +// imageUrl = null, +// ), +// WooPosItem.Variation( +// 3, +// name = "Product 3", +// price = "1.0$", +// imageUrl = null, +// ), +// ), +// loadingMore = false, +// reloadingProductsWithPullToRefresh = true, +// ) +// ) +// WooPosTheme { +// WooPosVariationsScreens( +// modifier = Modifier, +// viewModel = , +// onBackClicked = {}, +// variableProductData = VariableProductData( +// id = 0, +// name = "Variable Product", +// numOfVariations = 20, +// variationIds = emptyList() +// ), +// state = productState +// ) +// } +//} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt new file mode 100644 index 00000000000..d30522de294 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -0,0 +1,87 @@ +package com.woocommerce.android.ui.woopos.home.items.variations + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.woocommerce.android.R +import com.woocommerce.android.ui.products.variations.selector.VariationListHandler +import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById +import com.woocommerce.android.ui.woopos.home.items.WooPosItem +import com.woocommerce.android.ui.woopos.home.items.WooPosItemsUIEvent +import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState +import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class WooPosVariationsViewModel @Inject constructor( + private val getProductById: WooPosGetProductById, + private val variationListHandler: VariationListHandler +) : ViewModel() { + + private val _viewState = + MutableStateFlow(WooPosVariationsViewState.Loading(withCart = true)) + val viewState: StateFlow = _viewState + .stateIn( + viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = _viewState.value, + ) + + private var fetchJob: Job? = null + private var loadMoreJob: Job? = null + + fun init(productId: Long) { + fetchJob?.cancel() + + fetchJob = viewModelScope.launch { + println("PRODUCT ID AAA: $productId") + _viewState.value = WooPosVariationsViewState.Loading(withCart = true) + val product = getProductById(productId) + + variationListHandler.fetchVariations(productId, forceRefresh = true) + + variationListHandler.getVariationsFlow(productId).collect { variationList -> + _viewState.value = WooPosVariationsViewState.Content( + items = variationList.map { + WooPosItem.Variation( + id = it.remoteVariationId, + name = it.getName(product), + price = it.priceWithCurrency ?: "", + imageUrl = it.image?.source + ) + }, + loadingMore = false, + reloadingProductsWithPullToRefresh = false, + ) + } + } + } + + fun loadMore(productId: Long) { + loadMoreJob?.cancel() + loadMoreJob = viewModelScope.launch { + variationListHandler.loadMore(productId) + } + } + + fun onUIEvent(event: WooPosItemsUIEvent) { + when (event) { + is WooPosItemsUIEvent.EndOfItemsListReached -> { + onEndOfVariationsListReached() + } + else -> { + + } + } + } + + private fun onEndOfVariationsListReached() { + + } +} From 252027f359285f329e598208462e5441e613f619 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 11:34:03 +0530 Subject: [PATCH 09/80] Add a data source as a middle layer for fetching variations --- .../variations/WooPosVariationsDataSource.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt new file mode 100644 index 00000000000..6685068698d --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt @@ -0,0 +1,49 @@ +package com.woocommerce.android.ui.woopos.home.items.variations + +import com.woocommerce.android.model.ProductVariation +import com.woocommerce.android.ui.products.variations.selector.VariationListHandler +import com.woocommerce.android.util.WooLog +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WooPosVariationsDataSource @Inject constructor( + private val handler: VariationListHandler +) { + fun getVariationsFlow(productId: Long): Flow> { + return handler.getVariationsFlow(productId) + } + + suspend fun fetchVariations(productId: Long, forceRefresh: Boolean = true) { + val result = handler.fetchVariations(productId, forceRefresh = forceRefresh) + if (result.isSuccess) { + Result.success(Unit) + } else { + result.logFailure() + Result.failure( + result.exceptionOrNull() ?: Exception("Unknown error while loading more variations") + ) + } + } + + suspend fun loadMore(productId: Long): Result = withContext(Dispatchers.IO) { + val result = handler.loadMore(productId) + if (result.isSuccess) { + Result.success(Unit) + } else { + result.logFailure() + Result.failure( + result.exceptionOrNull() ?: Exception("Unknown error while loading more variations") + ) + } + } +} + +private fun Result.logFailure() { + val error = exceptionOrNull() + val errorMessage = error?.message ?: "Unknown error" + WooLog.e(WooLog.T.POS, "Loading variations failed - $errorMessage", error) +} From db3ad05b26be73c3787a198dd9fba9f94775a55f Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 11:34:15 +0530 Subject: [PATCH 10/80] Add UI events model for variations screen --- .../woopos/home/items/variations/WooPosVariationsUIEvents.kt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt new file mode 100644 index 00000000000..bb1d345a434 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt @@ -0,0 +1,5 @@ +package com.woocommerce.android.ui.woopos.home.items.variations + +sealed class WooPosVariationsUIEvents { + data class EndOfItemsListReached(val productId: Long) : WooPosVariationsUIEvents() +} From 7ee2e5710a6bcf6b1b1759596384382906437eb0 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 11:34:31 +0530 Subject: [PATCH 11/80] Use the variations source middle layer in the view model --- .../variations/WooPosVariationsViewModel.kt | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index d30522de294..e2c17d4d319 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -21,7 +21,7 @@ import javax.inject.Inject @HiltViewModel class WooPosVariationsViewModel @Inject constructor( private val getProductById: WooPosGetProductById, - private val variationListHandler: VariationListHandler + private val variationsDataSource: WooPosVariationsDataSource, ) : ViewModel() { private val _viewState = @@ -40,13 +40,12 @@ class WooPosVariationsViewModel @Inject constructor( fetchJob?.cancel() fetchJob = viewModelScope.launch { - println("PRODUCT ID AAA: $productId") _viewState.value = WooPosVariationsViewState.Loading(withCart = true) val product = getProductById(productId) - variationListHandler.fetchVariations(productId, forceRefresh = true) + variationsDataSource.fetchVariations(productId, forceRefresh = true) - variationListHandler.getVariationsFlow(productId).collect { variationList -> + variationsDataSource.getVariationsFlow(productId).collect { variationList -> _viewState.value = WooPosVariationsViewState.Content( items = variationList.map { WooPosItem.Variation( @@ -66,22 +65,24 @@ class WooPosVariationsViewModel @Inject constructor( fun loadMore(productId: Long) { loadMoreJob?.cancel() loadMoreJob = viewModelScope.launch { - variationListHandler.loadMore(productId) + val result = variationsDataSource.loadMore(productId) + if (result.isSuccess) { + Result.success(Unit) + } else { + WooPosVariationsViewState.Error() + } } } - fun onUIEvent(event: WooPosItemsUIEvent) { + fun onUIEvent(event: WooPosVariationsUIEvents) { when (event) { - is WooPosItemsUIEvent.EndOfItemsListReached -> { - onEndOfVariationsListReached() - } - else -> { - + is WooPosVariationsUIEvents.EndOfItemsListReached -> { + onEndOfVariationsListReached(event.productId) } } } - private fun onEndOfVariationsListReached() { - + private fun onEndOfVariationsListReached(productId: Long) { + loadMore(productId) } } From d1abab9508fca5dba30606037a8b4089d4cbd979 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 12:04:44 +0530 Subject: [PATCH 12/80] Integrate pull to refresh into the variations screen. --- .../variations/WooPosVariationsScreen.kt | 34 ++++++++++++++++-- .../variations/WooPosVariationsUIEvents.kt | 1 + .../variations/WooPosVariationsViewModel.kt | 36 ++++++++++++++----- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index afc5b5bc93e..946ecd068d4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -9,15 +9,20 @@ 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.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -47,21 +52,39 @@ fun WooPosVariationsScreen( viewModel.init(variableProductData.id) } val state = viewModel.viewState - WooPosVariationsScreens(modifier, viewModel, onBackClicked, variableProductData, state) + WooPosVariationsScreens( + modifier, + onBackClicked, + onEndOfItemListReached = { + viewModel.onUIEvent(WooPosVariationsUIEvents.EndOfItemsListReached(variableProductData.id)) + }, + onPullToRefresh = { + viewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(variableProductData.id)) + }, + variableProductData, + state + ) } +@OptIn(ExperimentalMaterialApi::class) @Composable private fun WooPosVariationsScreens( modifier: Modifier, - viewModel: WooPosVariationsViewModel, onBackClicked: () -> Unit, + onEndOfItemListReached: () -> Unit, + onPullToRefresh: () -> Unit, variableProductData: VariableProductData, state: StateFlow ) { val itemState = state.collectAsState() + val pullToRefreshState = rememberPullRefreshState( + itemState.value.reloadingProductsWithPullToRefresh, + onPullToRefresh + ) Box( modifier = modifier .fillMaxSize() + .pullRefresh(pullToRefreshState) .padding( start = 16.dp.toAdaptivePadding(), end = 16.dp.toAdaptivePadding(), @@ -81,13 +104,18 @@ private fun WooPosVariationsScreens( is WooPosVariationsViewState.Content -> { Spacer(modifier = Modifier.height(16.dp)) ItemsList(state = itemsState, onItemClicked = {}) { - viewModel.loadMore(variableProductData.id) + onEndOfItemListReached() } } else -> {} } } + PullRefreshIndicator( + modifier = Modifier.align(Alignment.TopCenter), + refreshing = itemState.value.reloadingProductsWithPullToRefresh, + state = pullToRefreshState + ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt index bb1d345a434..2bd6b77bec3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt @@ -2,4 +2,5 @@ package com.woocommerce.android.ui.woopos.home.items.variations sealed class WooPosVariationsUIEvents { data class EndOfItemsListReached(val productId: Long) : WooPosVariationsUIEvents() + data class PullToRefreshTriggered(val productId: Long) : WooPosVariationsUIEvents() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index e2c17d4d319..b910ef0a53e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -37,10 +37,18 @@ class WooPosVariationsViewModel @Inject constructor( private var loadMoreJob: Job? = null fun init(productId: Long) { + fetchVariations(productId = productId, withPullToRefresh = false, withCart = true) + } + + private fun fetchVariations(productId: Long, withPullToRefresh: Boolean, withCart: Boolean) { + _viewState.value = if (withPullToRefresh) { + buildProductsReloadingState() + } else { + WooPosVariationsViewState.Loading(withCart = withCart) + } fetchJob?.cancel() fetchJob = viewModelScope.launch { - _viewState.value = WooPosVariationsViewState.Loading(withCart = true) val product = getProductById(productId) variationsDataSource.fetchVariations(productId, forceRefresh = true) @@ -48,13 +56,13 @@ class WooPosVariationsViewModel @Inject constructor( variationsDataSource.getVariationsFlow(productId).collect { variationList -> _viewState.value = WooPosVariationsViewState.Content( items = variationList.map { - WooPosItem.Variation( - id = it.remoteVariationId, - name = it.getName(product), - price = it.priceWithCurrency ?: "", - imageUrl = it.image?.source - ) - }, + WooPosItem.Variation( + id = it.remoteVariationId, + name = it.getName(product), + price = it.priceWithCurrency ?: "", + imageUrl = it.image?.source + ) + }, loadingMore = false, reloadingProductsWithPullToRefresh = false, ) @@ -62,6 +70,14 @@ class WooPosVariationsViewModel @Inject constructor( } } + private fun buildProductsReloadingState() = + when (val state = viewState.value) { + is WooPosVariationsViewState.Content -> state.copy(reloadingProductsWithPullToRefresh = true) + is WooPosVariationsViewState.Loading -> state.copy(reloadingProductsWithPullToRefresh = true) + is WooPosVariationsViewState.Error -> state.copy(reloadingProductsWithPullToRefresh = true) + is WooPosVariationsViewState.Empty -> state.copy(reloadingProductsWithPullToRefresh = true) + } + fun loadMore(productId: Long) { loadMoreJob?.cancel() loadMoreJob = viewModelScope.launch { @@ -79,6 +95,10 @@ class WooPosVariationsViewModel @Inject constructor( is WooPosVariationsUIEvents.EndOfItemsListReached -> { onEndOfVariationsListReached(event.productId) } + + is WooPosVariationsUIEvents.PullToRefreshTriggered -> { + fetchVariations(event.productId, withPullToRefresh = true, withCart = false) + } } } From 4456ba122ff5fe2e56cadf92a296700662568863 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 13:38:51 +0530 Subject: [PATCH 13/80] Uncomment preview code --- .../variations/WooPosVariationsScreen.kt | 96 +++++++++---------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index 946ecd068d4..79ecd6dc562 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -28,15 +28,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.hilt.navigation.compose.hiltViewModel -import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.items.ItemsList import com.woocommerce.android.ui.woopos.home.items.WooPosItem -import com.woocommerce.android.ui.woopos.home.items.WooPosItem.SimpleProduct import com.woocommerce.android.ui.woopos.home.items.WooPosItemNavigationData.VariableProductData -import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -169,49 +166,50 @@ private fun VariationsToolbar( } } -//@Composable -//@WooPosPreview -//fun WooPosVariationsScreenPreview() { -// val productState = MutableStateFlow( -// WooPosVariationsViewState.Content( -// items = listOf( -// WooPosItem.Variation( -// 1, -// name = "Product 1, Product 1, Product 1, " + -// "Product 1, Product 1, Product 1, Product 1, Product 1" + -// "Product 1, Product 1, Product 1, Product 1, Product 1", -// price = "10.0$", -// imageUrl = null, -// ), -// WooPosItem.Variation( -// 2, -// name = "Product 2", -// price = "2000.00$", -// imageUrl = null, -// ), -// WooPosItem.Variation( -// 3, -// name = "Product 3", -// price = "1.0$", -// imageUrl = null, -// ), -// ), -// loadingMore = false, -// reloadingProductsWithPullToRefresh = true, -// ) -// ) -// WooPosTheme { -// WooPosVariationsScreens( -// modifier = Modifier, -// viewModel = , -// onBackClicked = {}, -// variableProductData = VariableProductData( -// id = 0, -// name = "Variable Product", -// numOfVariations = 20, -// variationIds = emptyList() -// ), -// state = productState -// ) -// } -//} +@Composable +@WooPosPreview +fun WooPosVariationsScreenPreview() { + val productState = MutableStateFlow( + WooPosVariationsViewState.Content( + items = listOf( + WooPosItem.Variation( + 1, + name = "Product 1, Product 1, Product 1, " + + "Product 1, Product 1, Product 1, Product 1, Product 1" + + "Product 1, Product 1, Product 1, Product 1, Product 1", + price = "10.0$", + imageUrl = null, + ), + WooPosItem.Variation( + 2, + name = "Product 2", + price = "2000.00$", + imageUrl = null, + ), + WooPosItem.Variation( + 3, + name = "Product 3", + price = "1.0$", + imageUrl = null, + ), + ), + loadingMore = false, + reloadingProductsWithPullToRefresh = true, + ) + ) + WooPosTheme { + WooPosVariationsScreens( + modifier = Modifier, + onBackClicked = {}, + onEndOfItemListReached = {}, + onPullToRefresh = {}, + variableProductData = VariableProductData( + id = 0, + name = "Variable Product", + numOfVariations = 20, + variationIds = emptyList() + ), + state = productState + ) + } +} From 77d714e72ad8c3ce4911c9c1406d2c1420a7f362 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 13:39:17 +0530 Subject: [PATCH 14/80] Add unit test to verify loading state is displayed when variations view model is initiated. --- .../WooPosVariationsViewModelTest.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt new file mode 100644 index 00000000000..f199534480b --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -0,0 +1,35 @@ +package com.woocommerce.android.ui.woopos.home.variations + +import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById +import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState +import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsDataSource +import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsViewModel +import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.mock + +@ExperimentalCoroutinesApi +class WooPosVariationsViewModelTest { + + @Rule + @JvmField + val coroutinesTestRule = WooPosCoroutineTestRule() + + private val getProductById: WooPosGetProductById = mock() + private val variationsDataSource: WooPosVariationsDataSource = mock() + private lateinit var wooPosVariationsViewModel: WooPosVariationsViewModel + + @Test + fun `given view model init, then loading state is displayed`() { + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + + assertThat( + wooPosVariationsViewModel.viewState.value + ).isEqualTo( + WooPosVariationsViewState.Loading(withCart = true) + ) + } +} From 12afd8abe1f74ac0048f1439596b1ee13b596ca1 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 13:41:22 +0530 Subject: [PATCH 15/80] Add unit test to verify loading state is displayed when variations view model is initiated. --- .../ui/woopos/home/variations/WooPosVariationsViewModelTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index f199534480b..aee88d3501e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -25,6 +25,7 @@ class WooPosVariationsViewModelTest { @Test fun `given view model init, then loading state is displayed`() { wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) assertThat( wooPosVariationsViewModel.viewState.value From 8875cee944ed4d6280096933e85060336d6a1016 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 13:44:14 +0530 Subject: [PATCH 16/80] Add unit test to verify API call is made to get product when variations view model is initiated. --- .../variations/WooPosVariationsViewModelTest.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index aee88d3501e..eaaf53b416e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -6,10 +6,14 @@ import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsD import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsViewModel import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @ExperimentalCoroutinesApi class WooPosVariationsViewModelTest { @@ -33,4 +37,13 @@ class WooPosVariationsViewModelTest { WooPosVariationsViewState.Loading(withCart = true) ) } + + @Test + fun `given view model init, then API call is made to fetch product`() = runTest { + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) + wooPosVariationsViewModel.init(1L) + + verify(getProductById).invoke(1L) + } } From 58c0b6f5ec97a236f7f7388aa0afc5ed2b96ca85 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 13:46:07 +0530 Subject: [PATCH 17/80] Add unit test to verify API call is made to fetch variations when variations view model is initiated. --- .../variations/WooPosVariationsViewModelTest.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index eaaf53b416e..454ead8a71e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -11,6 +11,8 @@ import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test +import org.mockito.ArgumentMatchers.eq +import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -40,10 +42,21 @@ class WooPosVariationsViewModelTest { @Test fun `given view model init, then API call is made to fetch product`() = runTest { - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) wooPosVariationsViewModel.init(1L) verify(getProductById).invoke(1L) } + + @Test + fun `given view model init, then API call is made to fetch variation`() = runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + + verify(variationsDataSource).fetchVariations(eq(1L), any()) + } } From e2a5e99a4bf0563dcb111f6380bb84b741b3dd44 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 13:47:03 +0530 Subject: [PATCH 18/80] Add unit test to verify API call is made to fetch variations with forceRefresh set to true when variations view model is initiated. --- .../home/variations/WooPosVariationsViewModelTest.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 454ead8a71e..2ae55858c50 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -59,4 +59,14 @@ class WooPosVariationsViewModelTest { verify(variationsDataSource).fetchVariations(eq(1L), any()) } + + @Test + fun `given view model init, then API call is made to fetch variation with forceRefresh set to true`() = runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + + verify(variationsDataSource).fetchVariations(1L, forceRefresh = true) + } } From 05e1f443890d7d6d4ba31f2abff9a72ae00ee917 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 14:20:11 +0530 Subject: [PATCH 19/80] Add unit test to verify that content state is displayed when the API call is successful. --- .../WooPosVariationsViewModelTest.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 2ae55858c50..2c5e4666734 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -1,5 +1,7 @@ package com.woocommerce.android.ui.woopos.home.variations +import app.cash.turbine.test +import com.woocommerce.android.ui.products.ProductTestUtils import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsDataSource @@ -7,6 +9,9 @@ import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsV import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -69,4 +74,30 @@ class WooPosVariationsViewModelTest { verify(variationsDataSource).fetchVariations(1L, forceRefresh = true) } + + @Test + fun `given view model init, when variation fetched successfully, then view state is updated with variation content`() = runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) + ) + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + advanceUntilIdle() + + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Content + assertThat(value).isInstanceOf(WooPosVariationsViewState.Content::class.java) + } + } } From fa9577bd2c40addfce265e4cb955252822c5b6ab Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 14:22:49 +0530 Subject: [PATCH 20/80] Add unit test to verify that content state is displayed with correct properties when the API call is successful. --- .../WooPosVariationsViewModelTest.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 2c5e4666734..57ad5e0db22 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -100,4 +100,33 @@ class WooPosVariationsViewModelTest { assertThat(value).isInstanceOf(WooPosVariationsViewState.Content::class.java) } } + + @Test + fun `given view model init, when variation fetched successfully, then view state is updated with proper variation content`() = runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) + ) + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + advanceUntilIdle() + + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Content + assertThat(value.items.size).isEqualTo(3) + assertThat(value.items[0].id).isEqualTo(2) + assertThat(value.items[1].id).isEqualTo(3) + assertThat(value.items[2].id).isEqualTo(4) + } + } } From 19239adde0065dc9ef7426bc6b6a0e12e86491c4 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 14:24:28 +0530 Subject: [PATCH 21/80] Add unit test to verify that content state is displayed with correct properties when the API call is successful. --- .../ui/woopos/home/variations/WooPosVariationsViewModelTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 57ad5e0db22..fda2134e045 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -21,6 +21,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import kotlin.test.assertFalse @ExperimentalCoroutinesApi class WooPosVariationsViewModelTest { @@ -127,6 +128,8 @@ class WooPosVariationsViewModelTest { assertThat(value.items[0].id).isEqualTo(2) assertThat(value.items[1].id).isEqualTo(3) assertThat(value.items[2].id).isEqualTo(4) + assertFalse(value.loadingMore) + assertFalse(value.reloadingProductsWithPullToRefresh) } } } From 820e0fa49368b4c178da5815886c90d15f43c85e Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 14:42:12 +0530 Subject: [PATCH 22/80] Add unit test to verify that content state is displayed with reloading property set to true when pull to refreshed --- .../WooPosVariationsViewModelTest.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index fda2134e045..4fa560f2979 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -5,6 +5,7 @@ import com.woocommerce.android.ui.products.ProductTestUtils import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsDataSource +import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsUIEvents import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsViewModel import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -22,6 +23,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import kotlin.test.assertFalse +import kotlin.test.assertTrue @ExperimentalCoroutinesApi class WooPosVariationsViewModelTest { @@ -132,4 +134,31 @@ class WooPosVariationsViewModelTest { assertFalse(value.reloadingProductsWithPullToRefresh) } } + + @Test + fun `given view state is content, when pull to refreshed, then view state is updated with proper variation content`() = runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) + ) + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) + advanceUntilIdle() + + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Content + assertTrue(value.reloadingProductsWithPullToRefresh) + } + } } From b18a888352b39c661dca8fcca862f1960fd422e3 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 15:00:18 +0530 Subject: [PATCH 23/80] Add unit test to verify that Loading state is displayed with reloading property set to true when pull to refreshed --- .../WooPosVariationsViewModelTest.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 4fa560f2979..94862f1cb45 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -161,4 +162,23 @@ class WooPosVariationsViewModelTest { assertTrue(value.reloadingProductsWithPullToRefresh) } } + + @Test + fun `given view state is Loading, when pull to refreshed, then view state is updated with proper variation content`() = runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + emptyFlow() + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) + + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Loading + assertTrue(value.reloadingProductsWithPullToRefresh) + } + } } From 925ee86b4da0d9b463f7121535a4037c02f324a1 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 16:46:11 +0530 Subject: [PATCH 24/80] Return result from fetchVariations method --- .../home/items/variations/WooPosVariationsDataSource.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt index 6685068698d..04bd5e5065a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt @@ -17,9 +17,9 @@ class WooPosVariationsDataSource @Inject constructor( return handler.getVariationsFlow(productId) } - suspend fun fetchVariations(productId: Long, forceRefresh: Boolean = true) { + suspend fun fetchVariations(productId: Long, forceRefresh: Boolean = true): Result { val result = handler.fetchVariations(productId, forceRefresh = forceRefresh) - if (result.isSuccess) { + return if (result.isSuccess) { Result.success(Unit) } else { result.logFailure() From 109f7b95f8d3428b77812fd33a74d3298f76e0c9 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 11 Nov 2024 16:46:28 +0530 Subject: [PATCH 25/80] Add unit test to verify that Error state is displayed with reloading property set to true when pull to refreshed --- .../variations/WooPosVariationsViewModel.kt | 33 ++++++++++--------- .../WooPosVariationsViewModelTest.kt | 30 +++++++++++++++++ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index b910ef0a53e..44a0a60105d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -51,21 +51,24 @@ class WooPosVariationsViewModel @Inject constructor( fetchJob = viewModelScope.launch { val product = getProductById(productId) - variationsDataSource.fetchVariations(productId, forceRefresh = true) - - variationsDataSource.getVariationsFlow(productId).collect { variationList -> - _viewState.value = WooPosVariationsViewState.Content( - items = variationList.map { - WooPosItem.Variation( - id = it.remoteVariationId, - name = it.getName(product), - price = it.priceWithCurrency ?: "", - imageUrl = it.image?.source - ) - }, - loadingMore = false, - reloadingProductsWithPullToRefresh = false, - ) + val result = variationsDataSource.fetchVariations(productId, forceRefresh = true) + if (result.isSuccess) { + variationsDataSource.getVariationsFlow(productId).collect { variationList -> + _viewState.value = WooPosVariationsViewState.Content( + items = variationList.map { + WooPosItem.Variation( + id = it.remoteVariationId, + name = it.getName(product), + price = it.priceWithCurrency ?: "", + imageUrl = it.image?.source + ) + }, + loadingMore = false, + reloadingProductsWithPullToRefresh = false, + ) + } + } else { + _viewState.value = WooPosVariationsViewState.Error() } } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 94862f1cb45..44d3329fbc7 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.woopos.home.variations +import androidx.lifecycle.asLiveData import app.cash.turbine.test import com.woocommerce.android.ui.products.ProductTestUtils import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById @@ -12,6 +13,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -181,4 +185,30 @@ class WooPosVariationsViewModelTest { assertTrue(value.reloadingProductsWithPullToRefresh) } } + + @Test + fun `given view state is Error, when pull to refreshed, then view state is updated with proper variation content`() = runTest { + val testDispatcher = StandardTestDispatcher(testScheduler) + + val testScope = TestScope(testDispatcher) + + testScope.launch { + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + whenever(variationsDataSource.fetchVariations(any(), any())).thenReturn( + Result.failure(Throwable()) + ) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) + + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Error + assertThat(value).isInstanceOf(WooPosVariationsViewState.Error::class.java) + assertTrue(value.reloadingProductsWithPullToRefresh) + } + } + } } From a57f85887537917e5db59e45163aab814d2810a7 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 09:03:41 +0530 Subject: [PATCH 26/80] Make all the tests pass --- .../WooPosVariationsViewModelTest.kt | 204 ++++++++++-------- 1 file changed, 115 insertions(+), 89 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 44d3329fbc7..9b5cf98494c 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -1,5 +1,7 @@ package com.woocommerce.android.ui.woopos.home.variations +import android.annotation.SuppressLint +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.asLiveData import app.cash.turbine.test import com.woocommerce.android.ui.products.ProductTestUtils @@ -11,13 +13,8 @@ import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsV import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.launch -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -33,6 +30,9 @@ import kotlin.test.assertTrue @ExperimentalCoroutinesApi class WooPosVariationsViewModelTest { + @Rule @JvmField + val rule = InstantTaskExecutorRule() + @Rule @JvmField val coroutinesTestRule = WooPosCoroutineTestRule() @@ -42,7 +42,9 @@ class WooPosVariationsViewModelTest { private lateinit var wooPosVariationsViewModel: WooPosVariationsViewModel @Test - fun `given view model init, then loading state is displayed`() { + fun `given view model init, then loading state is displayed`() = runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) wooPosVariationsViewModel.init(1L) @@ -84,115 +86,138 @@ class WooPosVariationsViewModelTest { } @Test - fun `given view model init, when variation fetched successfully, then view state is updated with variation content`() = runTest { - whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( - flowOf( - listOf( - ProductTestUtils.generateProductVariation(1L, 2L), - ProductTestUtils.generateProductVariation(1L, 3L), - ProductTestUtils.generateProductVariation(1L, 4L), + fun `given view model init, when variation fetched successfully, then view state is updated with variation content`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(any())).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) ) ) - ) - whenever(getProductById.invoke(any())).thenReturn( - ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") - ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) - wooPosVariationsViewModel.init(1L) - advanceUntilIdle() + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + advanceUntilIdle() - wooPosVariationsViewModel.viewState.test { - // THEN - val value = awaitItem() as WooPosVariationsViewState.Content - assertThat(value).isInstanceOf(WooPosVariationsViewState.Content::class.java) + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Content + assertThat(value).isInstanceOf(WooPosVariationsViewState.Content::class.java) + } } - } @Test - fun `given view model init, when variation fetched successfully, then view state is updated with proper variation content`() = runTest { - whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( - flowOf( - listOf( - ProductTestUtils.generateProductVariation(1L, 2L), - ProductTestUtils.generateProductVariation(1L, 3L), - ProductTestUtils.generateProductVariation(1L, 4L), + fun `given view model init, when variation fetched successfully, then view state is updated with proper variation content`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) ) ) - ) - whenever(getProductById.invoke(any())).thenReturn( - ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") - ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) - wooPosVariationsViewModel.init(1L) - advanceUntilIdle() + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + advanceUntilIdle() - wooPosVariationsViewModel.viewState.test { - // THEN - val value = awaitItem() as WooPosVariationsViewState.Content - assertThat(value.items.size).isEqualTo(3) - assertThat(value.items[0].id).isEqualTo(2) - assertThat(value.items[1].id).isEqualTo(3) - assertThat(value.items[2].id).isEqualTo(4) - assertFalse(value.loadingMore) - assertFalse(value.reloadingProductsWithPullToRefresh) + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Content + assertThat(value.items.size).isEqualTo(3) + assertThat(value.items[0].id).isEqualTo(2) + assertThat(value.items[1].id).isEqualTo(3) + assertThat(value.items[2].id).isEqualTo(4) + assertFalse(value.loadingMore) + assertFalse(value.reloadingProductsWithPullToRefresh) + } } - } + @SuppressLint("CheckResult") @Test - fun `given view state is content, when pull to refreshed, then view state is updated with proper variation content`() = runTest { - whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( - flowOf( - listOf( - ProductTestUtils.generateProductVariation(1L, 2L), - ProductTestUtils.generateProductVariation(1L, 3L), - ProductTestUtils.generateProductVariation(1L, 4L), + fun `given view state is content, when pull to refreshed, then view state is updated with proper variation content`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(any())).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) ) ) - ) - whenever(getProductById.invoke(any())).thenReturn( - ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") - ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + val states: MutableList = mutableListOf() + wooPosVariationsViewModel.viewState.asLiveData().observeForever { + states.add(it) + } - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) - wooPosVariationsViewModel.init(1L) - wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) - advanceUntilIdle() + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) - wooPosVariationsViewModel.viewState.test { - // THEN - val value = awaitItem() as WooPosVariationsViewState.Content - assertTrue(value.reloadingProductsWithPullToRefresh) + assertThat( + states.any { (it as? WooPosVariationsViewState.Content)?.reloadingProductsWithPullToRefresh == true } + ) } - } @Test - fun `given view state is Loading, when pull to refreshed, then view state is updated with proper variation content`() = runTest { - whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( - emptyFlow() - ) + fun `given view state is Loading, when pull to refreshed, then view state is updated with proper variation content`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + emptyFlow() + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) + + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Loading + assertTrue(value.reloadingProductsWithPullToRefresh) + } + } + + @Test + fun `given view state is Error, when fetch variations, then view state is updated with error state`() = runTest { + whenever(getProductById.invoke(any())).thenReturn( ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) + whenever(variationsDataSource.fetchVariations(any(), any())).thenReturn( + Result.failure(Throwable()) + ) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) wooPosVariationsViewModel.init(1L) - wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) wooPosVariationsViewModel.viewState.test { // THEN - val value = awaitItem() as WooPosVariationsViewState.Loading - assertTrue(value.reloadingProductsWithPullToRefresh) + val value = awaitItem() as WooPosVariationsViewState.Error + assertThat(value).isInstanceOf(WooPosVariationsViewState.Error::class.java) } } @Test - fun `given view state is Error, when pull to refreshed, then view state is updated with proper variation content`() = runTest { - val testDispatcher = StandardTestDispatcher(testScheduler) - - val testScope = TestScope(testDispatcher) - - testScope.launch { + fun `given view state is Error, when pull to refreshed, then view state is updated with proper variation content`() = + runTest { whenever(getProductById.invoke(any())).thenReturn( ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) @@ -200,15 +225,16 @@ class WooPosVariationsViewModelTest { Result.failure(Throwable()) ) wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + val states: MutableList = mutableListOf() + wooPosVariationsViewModel.viewState.asLiveData().observeForever { + states.add(it) + } + wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) - wooPosVariationsViewModel.viewState.test { - // THEN - val value = awaitItem() as WooPosVariationsViewState.Error - assertThat(value).isInstanceOf(WooPosVariationsViewState.Error::class.java) - assertTrue(value.reloadingProductsWithPullToRefresh) - } + assertThat( + states.any { (it as? WooPosVariationsViewState.Error)?.reloadingProductsWithPullToRefresh == true } + ) } - } } From 8b5f63220ab2275603c227dd775ab96251eb81d7 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 09:12:29 +0530 Subject: [PATCH 27/80] Add test to verify error state is displayed when load more call fails --- .../WooPosVariationsViewModelTest.kt | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 9b5cf98494c..75ad29f2e99 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -146,7 +146,7 @@ class WooPosVariationsViewModelTest { @SuppressLint("CheckResult") @Test - fun `given view state is content, when pull to refreshed, then view state is updated with proper variation content`() = + fun `given view state is content, when pull to refreshed, then view state contains Content state with pull to refresh set to true`() = runTest { whenever(variationsDataSource.getVariationsFlow(any())).thenReturn( flowOf( @@ -175,7 +175,7 @@ class WooPosVariationsViewModelTest { } @Test - fun `given view state is Loading, when pull to refreshed, then view state is updated with proper variation content`() = + fun `given view state is Loading, when pull to refreshed, then view state contains Loading state with pull to refresh set to true`() = runTest { whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( emptyFlow() @@ -237,4 +237,25 @@ class WooPosVariationsViewModelTest { states.any { (it as? WooPosVariationsViewState.Error)?.reloadingProductsWithPullToRefresh == true } ) } + + @SuppressLint("CheckResult") + @Test + fun `given load more variations, when failure, then view state is updated with error state`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + emptyFlow() + ) + whenever(variationsDataSource.loadMore(any())).thenReturn( + Result.failure(Throwable()) + ) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.loadMore(1L) + + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Error + assertThat(value).isInstanceOf(WooPosVariationsViewState.Error::class.java) + } + } } From d684761b8efc8761aab9d77b930184a0ac003c12 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 09:12:37 +0530 Subject: [PATCH 28/80] Add test to verify error state is displayed when load more call fails --- .../woopos/home/items/variations/WooPosVariationsViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 44a0a60105d..88c40080e53 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -88,7 +88,7 @@ class WooPosVariationsViewModel @Inject constructor( if (result.isSuccess) { Result.success(Unit) } else { - WooPosVariationsViewState.Error() + _viewState.value = WooPosVariationsViewState.Error() } } } From 80597ccb51718030e9e14ff4eb8f9671b4cb1be7 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 09:40:42 +0530 Subject: [PATCH 29/80] Add test to verify load more method is called when end of list is reached. --- .../variations/WooPosVariationsViewModelTest.kt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 75ad29f2e99..2c3f920e2b8 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -238,7 +238,6 @@ class WooPosVariationsViewModelTest { ) } - @SuppressLint("CheckResult") @Test fun `given load more variations, when failure, then view state is updated with error state`() = runTest { @@ -258,4 +257,20 @@ class WooPosVariationsViewModelTest { assertThat(value).isInstanceOf(WooPosVariationsViewState.Error::class.java) } } + + @Test + fun `when end of list reached, then load more called`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + emptyFlow() + ) + whenever(variationsDataSource.loadMore(any())).thenReturn( + Result.failure(Throwable()) + ) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.EndOfItemsListReached(1L)) + + verify(variationsDataSource).loadMore(1L) + } } From 502789ba44727fa00f768d3c23e20b7fdfe84e3f Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 12:36:29 +0530 Subject: [PATCH 30/80] Extract ItemsLoadingIndicator from WooPosItemsScreen to WooPosItemsList.kt --- .../ui/woopos/home/items/WooPosItemsList.kt | 16 ++++++++++++++++ .../ui/woopos/home/items/WooPosItemsScreen.kt | 16 ---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt index 89f6e85455d..eb0cc57558c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt @@ -246,6 +246,22 @@ private fun VariableProductDetails(item: VariableProduct) { ) } +@Composable +fun ItemsLoadingIndicator(itemsCount: Int = 10) { + WooPosLazyColumn( + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(2.dp), + ) { + items(itemsCount) { + ItemsLoadingItem() + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + @Composable fun ItemsLoadingItem() { WooPosCard( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt index 3b3cc89b148..7104365f0df 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt @@ -291,22 +291,6 @@ private fun SimpleProductsBanner( } } -@Composable -fun ItemsLoadingIndicator() { - WooPosLazyColumn( - verticalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(2.dp), - ) { - items(10) { - ItemsLoadingItem() - } - - item { - Spacer(modifier = Modifier.height(16.dp)) - } - } -} - @Composable fun ProductsEmptyList() { Box( From 0f9c24f5ff65cba54f63097d9cad370414ea6274 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 12:37:03 +0530 Subject: [PATCH 31/80] Display as many loading shimmer as variations or 10 whichever is minimum. --- .../woopos/home/items/variations/WooPosVariationsScreen.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index 79ecd6dc562..0e9ff0a6cd6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -32,6 +32,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.items.ItemsList +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.WooPosItemNavigationData.VariableProductData import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState @@ -105,6 +106,10 @@ private fun WooPosVariationsScreens( } } + is WooPosVariationsViewState.Loading -> ItemsLoadingIndicator( + minOf(10, variableProductData.numOfVariations) + ) + else -> {} } } From cac69152cf7c31c0fbff35cc2d1bf91ee746979b Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 13:06:16 +0530 Subject: [PATCH 32/80] Fix detekt errors --- .../android/ui/woopos/home/items/WooPosItemsList.kt | 2 +- .../android/ui/woopos/home/items/WooPosItemsScreen.kt | 2 -- .../android/ui/woopos/home/items/WooPosItemsViewState.kt | 2 +- .../woopos/home/items/variations/WooPosVariationsViewModel.kt | 4 ---- .../woopos/home/variations/WooPosVariationsViewModelTest.kt | 1 - 5 files changed, 2 insertions(+), 9 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt index eb0cc57558c..63aadf7b29e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt @@ -211,7 +211,7 @@ private fun ProductImage(item: WooPosItem) { val imageUrl = when (item) { is SimpleProduct -> item.imageUrl is VariableProduct -> item.imageUrl - is WooPosItem.Variation -> {""} + is WooPosItem.Variation -> { "" } } AsyncImage( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt index 7104365f0df..7b0747d24e3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight @@ -48,7 +47,6 @@ import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme 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.WooPosLazyColumn 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 diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewState.kt index f84e7107a9e..32b7a1c4196 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsViewState.kt @@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes sealed class WooPosItemsViewState( - override val reloadingProductsWithPullToRefresh: Boolean, + override val reloadingProductsWithPullToRefresh: Boolean, ) : WooPosBaseViewState(reloadingProductsWithPullToRefresh) { data class Content( override val items: List, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 88c40080e53..32562227b57 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -2,12 +2,8 @@ package com.woocommerce.android.ui.woopos.home.items.variations import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.woocommerce.android.R -import com.woocommerce.android.ui.products.variations.selector.VariationListHandler import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.ui.woopos.home.items.WooPosItem -import com.woocommerce.android.ui.woopos.home.items.WooPosItemsUIEvent -import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 2c3f920e2b8..d8df91f622a 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -197,7 +197,6 @@ class WooPosVariationsViewModelTest { @Test fun `given view state is Error, when fetch variations, then view state is updated with error state`() = runTest { - whenever(getProductById.invoke(any())).thenReturn( ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) From efe4b480ab44934a074f996d8fced962de0457d1 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 13:46:48 +0530 Subject: [PATCH 33/80] Handle edge cases of loading more variations --- .../variations/selector/VariationListHandler.kt | 4 ++++ .../items/variations/WooPosVariationsDataSource.kt | 4 ++++ .../items/variations/WooPosVariationsViewModel.kt | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/selector/VariationListHandler.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/selector/VariationListHandler.kt index 3081307c7b0..f1d961e731c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/selector/VariationListHandler.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/selector/VariationListHandler.kt @@ -15,6 +15,10 @@ class VariationListHandler @Inject constructor(private val repository: Variation fun getVariationsFlow(productId: Long) = repository.observeVariations(productId) + fun canLoadMore(): Boolean { + return canLoadMore + } + suspend fun fetchVariations(productId: Long, forceRefresh: Boolean = false): Result = mutex.withLock { // Reset the offset offset = 0 diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt index 04bd5e5065a..6890e9947f5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsDataSource.kt @@ -17,6 +17,10 @@ class WooPosVariationsDataSource @Inject constructor( return handler.getVariationsFlow(productId) } + fun canLoadMore(): Boolean { + return handler.canLoadMore() + } + suspend fun fetchVariations(productId: Long, forceRefresh: Boolean = true): Result { val result = handler.fetchVariations(productId, forceRefresh = forceRefresh) return if (result.isSuccess) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 32562227b57..5cda8f1d560 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.ui.woopos.home.items.WooPosItem +import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job @@ -78,11 +79,22 @@ class WooPosVariationsViewModel @Inject constructor( } fun loadMore(productId: Long) { + val currentState = _viewState.value + if (currentState !is WooPosVariationsViewState.Content) { + return + } + if (!variationsDataSource.canLoadMore()) { + return + } + _viewState.value = currentState.copy(loadingMore = true) loadMoreJob?.cancel() loadMoreJob = viewModelScope.launch { val result = variationsDataSource.loadMore(productId) if (result.isSuccess) { Result.success(Unit) + if (!variationsDataSource.canLoadMore()) { + _viewState.value = currentState.copy(loadingMore = false) + } } else { _viewState.value = WooPosVariationsViewState.Error() } From b46916d26453eef2339cc6ac473ddb6d769cbebf Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 13:47:07 +0530 Subject: [PATCH 34/80] Add test to verify load more is not called if the view state is anything that is not Content --- .../variations/WooPosVariationsViewModelTest.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index d8df91f622a..66ca5d26de5 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -22,6 +22,7 @@ import org.junit.Test import org.mockito.ArgumentMatchers.eq import org.mockito.kotlin.any import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import kotlin.test.assertFalse @@ -272,4 +273,20 @@ class WooPosVariationsViewModelTest { verify(variationsDataSource).loadMore(1L) } + + @Test + fun `given view state that is not Content, when load more is called, then return with doing nothing`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + emptyFlow() + ) + whenever(variationsDataSource.loadMore(any())).thenReturn( + Result.failure(Throwable()) + ) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.loadMore(1L) + + verify(variationsDataSource, never()).loadMore(1L) + } } From 89f1c0489cdaa88a4c0fa627716b670633b3900e Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 13:49:08 +0530 Subject: [PATCH 35/80] Add test to verify load more is not called loadMore function returns false --- .../WooPosVariationsViewModelTest.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 66ca5d26de5..4af61e7eeae 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -283,6 +283,25 @@ class WooPosVariationsViewModelTest { whenever(variationsDataSource.loadMore(any())).thenReturn( Result.failure(Throwable()) ) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.loadMore(1L) + + verify(variationsDataSource, never()).loadMore(1L) + } + + @Test + fun `given no more items to load, when load more is called, then return with doing nothing`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + emptyFlow() + ) + whenever(variationsDataSource.loadMore(any())).thenReturn( + Result.failure(Throwable()) + ) + whenever(variationsDataSource.canLoadMore()).thenReturn(false) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.loadMore(1L) From 879b88c967ffae473ab33a33c83f0410909635a8 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 13:56:10 +0530 Subject: [PATCH 36/80] Add test to verify proper view state when load more --- .../WooPosVariationsViewModelTest.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 4af61e7eeae..e1a9b689e58 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -308,4 +308,34 @@ class WooPosVariationsViewModelTest { verify(variationsDataSource, never()).loadMore(1L) } + + @Test + fun `given more items to load, when load more is called, then view state is updated properly`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) + ) + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + whenever(variationsDataSource.loadMore(any())).thenReturn( + Result.success(Unit) + ) + whenever(variationsDataSource.canLoadMore()).thenReturn(true) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.loadMore(1L) + + wooPosVariationsViewModel.viewState.test { + val value = awaitItem() as WooPosVariationsViewState.Content + assertTrue(value.loadingMore) + } + } } From d55ee98908ef36db5c42bd634a5613c28aa7026b Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 13:57:58 +0530 Subject: [PATCH 37/80] Add test to verify proper view state when load more call fails --- .../WooPosVariationsViewModelTest.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index e1a9b689e58..1aae010d609 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -338,4 +338,34 @@ class WooPosVariationsViewModelTest { assertTrue(value.loadingMore) } } + + @Test + fun `given load more call fails, when load more is called, then view state is updated properly`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) + ) + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + whenever(variationsDataSource.loadMore(any())).thenReturn( + Result.failure(Throwable()) + ) + whenever(variationsDataSource.canLoadMore()).thenReturn(true) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.loadMore(1L) + + wooPosVariationsViewModel.viewState.test { + val value = awaitItem() as WooPosVariationsViewState.Error + assertThat(value).isInstanceOf(WooPosVariationsViewState.Error::class.java) + } + } } From 0d1d4f1b151efd540f4fafc78cc9564539b77246 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 14:01:49 +0530 Subject: [PATCH 38/80] Remove unused import --- .../ui/woopos/home/items/variations/WooPosVariationsViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 5cda8f1d560..2b998b9eaf7 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.ui.woopos.home.items.WooPosItem -import com.woocommerce.android.ui.woopos.home.items.WooPosItemsViewState import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job From 6dea4479e831ff90653338ceef7d02a3765bdc45 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 15:04:12 +0530 Subject: [PATCH 39/80] Add string resource for pagination error message --- WooCommerce/src/main/res/values/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index e25840f4fd7..7ade8cc6843 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4289,6 +4289,8 @@ Popup menu with options. Swipe to navigate through items. It looks like you\'re not connected to the internet. Ensure your Wi-Fi is turned on. If you\'re using mobile data, make sure it\'s enabled in your device settings. + "Failed to load more items. Please try again." + Customer Orders From 819c87704521bff929c9a8b1d621778fb482a165 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 15:05:31 +0530 Subject: [PATCH 40/80] Handle snackbar message when there is pagination error. --- .../home/items/WooPosVariationsViewState.kt | 2 +- .../variations/WooPosVariationsScreen.kt | 122 +++++++++++++----- .../variations/WooPosVariationsViewModel.kt | 13 +- 3 files changed, 100 insertions(+), 37 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosVariationsViewState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosVariationsViewState.kt index 5f3be32c26b..58d7e35773f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosVariationsViewState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosVariationsViewState.kt @@ -7,7 +7,7 @@ sealed class WooPosVariationsViewState( data class Content( override val items: List, override val loadingMore: Boolean, - override val reloadingProductsWithPullToRefresh: Boolean = false + override val reloadingProductsWithPullToRefresh: Boolean = false, ) : WooPosVariationsViewState(reloadingProductsWithPullToRefresh), ContentViewState data class Loading( diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index 0e9ff0a6cd6..a45da9cfbdd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.woopos.home.items.variations +import android.annotation.SuppressLint import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -13,6 +14,10 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.SnackbarDuration +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -22,12 +27,18 @@ import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle +import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.toAdaptivePadding @@ -46,9 +57,27 @@ fun WooPosVariationsScreen( onBackClicked: () -> Unit ) { val viewModel: WooPosVariationsViewModel = hiltViewModel() + val snackbarHostState = remember { SnackbarHostState() } + val lifecycleOwner = LocalLifecycleOwner.current + val paginationErrorMessage = stringResource(id = R.string.woopos_variations_screen_pagination_error) + LaunchedEffect(variableProductData.id) { viewModel.init(variableProductData.id) } + LaunchedEffect(Unit) { + viewModel.events + .flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + .collect { event -> + when (event) { + is WooPosVariationsViewModel.WooPosVariationEvents.PaginationError -> { + snackbarHostState.showSnackbar( + message = paginationErrorMessage, + duration = SnackbarDuration.Short + ) + } + } + } + } val state = viewModel.viewState WooPosVariationsScreens( modifier, @@ -60,10 +89,12 @@ fun WooPosVariationsScreen( viewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(variableProductData.id)) }, variableProductData, - state + state, + snackbarHostState, ) } +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") @OptIn(ExperimentalMaterialApi::class) @Composable private fun WooPosVariationsScreens( @@ -72,52 +103,72 @@ private fun WooPosVariationsScreens( onEndOfItemListReached: () -> Unit, onPullToRefresh: () -> Unit, variableProductData: VariableProductData, - state: StateFlow + state: StateFlow, + snackbarHostState: SnackbarHostState, ) { val itemState = state.collectAsState() val pullToRefreshState = rememberPullRefreshState( itemState.value.reloadingProductsWithPullToRefresh, onPullToRefresh ) - Box( - modifier = modifier - .fillMaxSize() - .pullRefresh(pullToRefreshState) - .padding( - start = 16.dp.toAdaptivePadding(), - end = 16.dp.toAdaptivePadding(), - top = 30.dp.toAdaptivePadding(), - bottom = 0.dp.toAdaptivePadding(), - ) + Scaffold( + snackbarHost = { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 80.dp) + ) { + SnackbarHost( + hostState = snackbarHostState, + modifier = Modifier.align(Alignment.Center) + ) + } + } ) { - BackHandler(onBack = onBackClicked) - Column( - modifier = modifier.fillMaxHeight() + Box( + modifier = modifier + .fillMaxSize() + .pullRefresh(pullToRefreshState) + .padding( + start = 16.dp.toAdaptivePadding(), + end = 16.dp.toAdaptivePadding(), + top = 30.dp.toAdaptivePadding(), + bottom = 0.dp.toAdaptivePadding(), + ) ) { - VariationsToolbar( - variableProductData = variableProductData, - onBackClicked = onBackClicked - ) - when (val itemsState = itemState.value) { - is WooPosVariationsViewState.Content -> { - Spacer(modifier = Modifier.height(16.dp)) - ItemsList(state = itemsState, onItemClicked = {}) { - onEndOfItemListReached() + BackHandler(onBack = onBackClicked) + Column( + modifier = modifier.fillMaxHeight() + ) { + VariationsToolbar( + variableProductData = variableProductData, + onBackClicked = onBackClicked + ) + when (val itemsState = itemState.value) { + is WooPosVariationsViewState.Content -> { + Spacer(modifier = Modifier.height(16.dp)) + ItemsList(state = itemsState, onItemClicked = {}) { + onEndOfItemListReached() + } } - } - is WooPosVariationsViewState.Loading -> ItemsLoadingIndicator( - minOf(10, variableProductData.numOfVariations) - ) + is WooPosVariationsViewState.Loading -> ItemsLoadingIndicator( + minOf(10, variableProductData.numOfVariations) + ) + + is WooPosVariationsViewState.Error -> { - else -> {} + } + + else -> {} + } } + PullRefreshIndicator( + modifier = Modifier.align(Alignment.TopCenter), + refreshing = itemState.value.reloadingProductsWithPullToRefresh, + state = pullToRefreshState + ) } - PullRefreshIndicator( - modifier = Modifier.align(Alignment.TopCenter), - refreshing = itemState.value.reloadingProductsWithPullToRefresh, - state = pullToRefreshState - ) } } @@ -214,7 +265,8 @@ fun WooPosVariationsScreenPreview() { numOfVariations = 20, variationIds = emptyList() ), - state = productState + state = productState, + snackbarHostState = SnackbarHostState() ) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 2b998b9eaf7..f4e811a9daf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -5,11 +5,14 @@ import androidx.lifecycle.viewModelScope import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.ui.woopos.home.items.WooPosItem import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState +import com.woocommerce.android.viewmodel.MultiLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @@ -29,6 +32,9 @@ class WooPosVariationsViewModel @Inject constructor( initialValue = _viewState.value, ) + private val _events: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = 1) + val events = _events.asSharedFlow() + private var fetchJob: Job? = null private var loadMoreJob: Job? = null @@ -95,7 +101,8 @@ class WooPosVariationsViewModel @Inject constructor( _viewState.value = currentState.copy(loadingMore = false) } } else { - _viewState.value = WooPosVariationsViewState.Error() + _events.tryEmit(WooPosVariationEvents.PaginationError) + _viewState.value = currentState.copy(loadingMore = false) } } } @@ -115,4 +122,8 @@ class WooPosVariationsViewModel @Inject constructor( private fun onEndOfVariationsListReached(productId: Long) { loadMore(productId) } + + sealed class WooPosVariationEvents { + data object PaginationError : WooPosVariationEvents() + } } From c4bd13341189be53d1353b3e7629dfd8d24d29b9 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 16:29:37 +0530 Subject: [PATCH 41/80] Remove test --- .../variations/WooPosVariationsViewModel.kt | 4 +- .../WooPosVariationsViewModelTest.kt | 42 ++++++++----------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index f4e811a9daf..3d4b424f63c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -32,7 +32,9 @@ class WooPosVariationsViewModel @Inject constructor( initialValue = _viewState.value, ) - private val _events: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = 1) + private val _events: MutableSharedFlow = MutableSharedFlow( + extraBufferCapacity = 1 + ) val events = _events.asSharedFlow() private var fetchJob: Job? = null diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 1aae010d609..91591d1cfce 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -12,8 +12,12 @@ import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsU import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsViewModel import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat @@ -238,32 +242,22 @@ class WooPosVariationsViewModelTest { ) } - @Test - fun `given load more variations, when failure, then view state is updated with error state`() = - runTest { - whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( - emptyFlow() - ) - whenever(variationsDataSource.loadMore(any())).thenReturn( - Result.failure(Throwable()) - ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) - wooPosVariationsViewModel.init(1L) - wooPosVariationsViewModel.loadMore(1L) - - wooPosVariationsViewModel.viewState.test { - // THEN - val value = awaitItem() as WooPosVariationsViewState.Error - assertThat(value).isInstanceOf(WooPosVariationsViewState.Error::class.java) - } - } - @Test fun `when end of list reached, then load more called`() = runTest { - whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn( - emptyFlow() + whenever(variationsDataSource.getVariationsFlow(any())).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) + ) + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) + whenever(variationsDataSource.canLoadMore()).thenReturn(true) whenever(variationsDataSource.loadMore(any())).thenReturn( Result.failure(Throwable()) ) @@ -364,8 +358,8 @@ class WooPosVariationsViewModelTest { wooPosVariationsViewModel.loadMore(1L) wooPosVariationsViewModel.viewState.test { - val value = awaitItem() as WooPosVariationsViewState.Error - assertThat(value).isInstanceOf(WooPosVariationsViewState.Error::class.java) + val value = awaitItem() as WooPosVariationsViewState.Content + assertFalse(value.loadingMore) } } } From b71ca7d2f0c38fb1abaa5ecf76a7d1e88a6cf318 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 16:31:33 +0530 Subject: [PATCH 42/80] Fix detekt errors --- .../ui/woopos/home/items/variations/WooPosVariationsScreen.kt | 1 - .../woopos/home/items/variations/WooPosVariationsViewModel.kt | 1 - .../woopos/home/variations/WooPosVariationsViewModelTest.kt | 4 ---- 3 files changed, 6 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index a45da9cfbdd..ba13ff878e3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -157,7 +157,6 @@ private fun WooPosVariationsScreens( ) is WooPosVariationsViewState.Error -> { - } else -> {} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 3d4b424f63c..490979942c8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.ui.woopos.home.items.WooPosItem import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState -import com.woocommerce.android.viewmodel.MultiLiveEvent import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 91591d1cfce..b56cbaa8c5a 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -12,12 +12,8 @@ import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsU import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsViewModel import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.launch import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat From 58f50efa25e88ce4cb557e02fa05e0f2fc57da81 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 16:52:25 +0530 Subject: [PATCH 43/80] Add string resource for variations loading error screen --- WooCommerce/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 7ade8cc6843..feba074197f 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4290,6 +4290,7 @@ It looks like you\'re not connected to the internet. Ensure your Wi-Fi is turned on. If you\'re using mobile data, make sure it\'s enabled in your device settings. "Failed to load more items. Please try again." + Error loading variations Customer From 657557597f5c09c6ef18ab05f712db2f06aac69b Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 16:52:47 +0530 Subject: [PATCH 44/80] Add event for retry button click --- .../ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt index 2bd6b77bec3..fecc02742a1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsUIEvents.kt @@ -3,4 +3,5 @@ package com.woocommerce.android.ui.woopos.home.items.variations sealed class WooPosVariationsUIEvents { data class EndOfItemsListReached(val productId: Long) : WooPosVariationsUIEvents() data class PullToRefreshTriggered(val productId: Long) : WooPosVariationsUIEvents() + data class VariationsLoadingErrorRetryButtonClicked(val productId: Long) : WooPosVariationsUIEvents() } From 773a41b54f2649ece4ccaff4f8ad80cda170df65 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 16:52:58 +0530 Subject: [PATCH 45/80] Handle retry logic for variations --- .../variations/WooPosVariationsScreen.kt | 31 +++++++++++++++++++ .../variations/WooPosVariationsViewModel.kt | 4 +++ 2 files changed, 35 insertions(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index ba13ff878e3..f725206bc62 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -10,6 +10,7 @@ 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.width import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton @@ -41,6 +42,8 @@ import androidx.lifecycle.flowWithLifecycle import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme +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.toAdaptivePadding import com.woocommerce.android.ui.woopos.home.items.ItemsList import com.woocommerce.android.ui.woopos.home.items.ItemsLoadingIndicator @@ -88,6 +91,11 @@ fun WooPosVariationsScreen( onPullToRefresh = { viewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(variableProductData.id)) }, + onRetryClicked = { + viewModel.onUIEvent( + WooPosVariationsUIEvents.VariationsLoadingErrorRetryButtonClicked(variableProductData.id) + ) + }, variableProductData, state, snackbarHostState, @@ -102,6 +110,7 @@ private fun WooPosVariationsScreens( onBackClicked: () -> Unit, onEndOfItemListReached: () -> Unit, onPullToRefresh: () -> Unit, + onRetryClicked: () -> Unit, variableProductData: VariableProductData, state: StateFlow, snackbarHostState: SnackbarHostState, @@ -157,6 +166,9 @@ private fun WooPosVariationsScreens( ) is WooPosVariationsViewState.Error -> { + VariationsError { + onRetryClicked() + } } else -> {} @@ -171,6 +183,24 @@ private fun WooPosVariationsScreens( } } +@Composable +fun VariationsError(onRetryClicked: () -> Unit) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + WooPosErrorScreen( + modifier = Modifier.width(640.dp), + message = stringResource(id = R.string.woopos_variations_loading_error_title), + reason = stringResource(id = R.string.woopos_products_loading_error_message), + primaryButton = Button( + text = stringResource(id = R.string.woopos_products_loading_error_retry_button), + click = onRetryClicked + ) + ) + } +} + @Composable private fun VariationsToolbar( variableProductData: VariableProductData, @@ -258,6 +288,7 @@ fun WooPosVariationsScreenPreview() { onBackClicked = {}, onEndOfItemListReached = {}, onPullToRefresh = {}, + onRetryClicked = {}, variableProductData = VariableProductData( id = 0, name = "Variable Product", diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 490979942c8..0cd183c6742 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -117,6 +117,10 @@ class WooPosVariationsViewModel @Inject constructor( is WooPosVariationsUIEvents.PullToRefreshTriggered -> { fetchVariations(event.productId, withPullToRefresh = true, withCart = false) } + + is WooPosVariationsUIEvents.VariationsLoadingErrorRetryButtonClicked -> { + fetchVariations(event.productId, withPullToRefresh = false, withCart = false) + } } } From ef20d8e03c173bd79111c64163f66adbf83824da Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Tue, 12 Nov 2024 16:53:12 +0530 Subject: [PATCH 46/80] Add test to verify retry button click would call fetchVariations --- .../WooPosVariationsViewModelTest.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index b56cbaa8c5a..d2e4fd0c7b1 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -23,6 +23,7 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.never +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import kotlin.test.assertFalse @@ -145,6 +146,28 @@ class WooPosVariationsViewModelTest { } } + @Test + fun `given variation fetch fails, when retry clicked, then fetch variations called`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(any())).thenReturn( + emptyFlow() + ) + whenever(variationsDataSource.fetchVariations(any(), any())).thenReturn( + Result.failure(Throwable()) + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + + wooPosVariationsViewModel.init(1L) + wooPosVariationsViewModel.onUIEvent( + WooPosVariationsUIEvents.VariationsLoadingErrorRetryButtonClicked(1L) + ) + + verify(variationsDataSource, times(2)).fetchVariations(1L) + } + @SuppressLint("CheckResult") @Test fun `given view state is content, when pull to refreshed, then view state contains Content state with pull to refresh set to true`() = From aed945a7990d7bb79dec102d7c675bc108b52c89 Mon Sep 17 00:00:00 2001 From: Alejo Date: Sun, 17 Nov 2024 10:57:23 -0600 Subject: [PATCH 47/80] update purchase controls visibility --- .../android/ui/orders/wooshippinglabels/PurchaseSection.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt index c52d302a23f..e621a00711b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt @@ -27,7 +27,6 @@ internal fun PurchasesSection( modifier: Modifier = Modifier ) { Column(modifier) { - Divider() MarkComplete( markOrderComplete = markOrderComplete, onMarkOrderCompleteChange = onMarkOrderCompleteChange @@ -76,7 +75,7 @@ fun PurchasesSectionLandscapePreview() { } @Composable -private fun MarkComplete( +internal fun MarkComplete( markOrderComplete: Boolean, onMarkOrderCompleteChange: (Boolean) -> Unit, modifier: Modifier = Modifier @@ -102,7 +101,7 @@ private fun MarkComplete( } @Composable -private fun PurchaseButton( +internal fun PurchaseButton( total: String?, onPurchaseShippingLabel: () -> Unit, modifier: Modifier = Modifier @@ -127,7 +126,7 @@ private fun PurchaseButton( @Preview @Composable -private fun PurchasesSectionPreview() { +internal fun PurchasesSectionPreview() { WooThemeWithBackground { PurchasesSection( total = null, From 173b4a4bc7108d154c81822c02cebcd4a398fdd3 Mon Sep 17 00:00:00 2001 From: Alejo Date: Sun, 17 Nov 2024 10:58:04 -0600 Subject: [PATCH 48/80] modify purchase button visibility --- .../orders/wooshippinglabels/ShipmentDetails.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt index 6895ddc841c..add97abe02b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt @@ -57,10 +57,11 @@ fun ShipmentDetails( onMarkOrderCompleteChange: (Boolean) -> Unit, onPurchaseShippingLabel: () -> Unit, modifier: Modifier = Modifier, + handlerModifier: Modifier = Modifier, ) { val scope = rememberCoroutineScope() Column( - modifier + handlerModifier .clickable( onClick = { scope.launch { @@ -91,15 +92,16 @@ fun ShipmentDetails( } if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) { ShipmentDetailsLandscape( + modifier = modifier, markOrderComplete = markOrderComplete, onMarkOrderCompleteChange = onMarkOrderCompleteChange, onPurchaseShippingLabel = onPurchaseShippingLabel ) } else { ShipmentDetailsPortrait( + modifier = modifier, markOrderComplete = markOrderComplete, - onMarkOrderCompleteChange = onMarkOrderCompleteChange, - onPurchaseShippingLabel = onPurchaseShippingLabel + onMarkOrderCompleteChange = onMarkOrderCompleteChange ) } } @@ -108,7 +110,6 @@ fun ShipmentDetails( private fun ShipmentDetailsPortrait( markOrderComplete: Boolean, onMarkOrderCompleteChange: (Boolean) -> Unit, - onPurchaseShippingLabel: () -> Unit, modifier: Modifier = Modifier, ) { Column(modifier) { @@ -134,11 +135,10 @@ private fun ShipmentDetailsPortrait( modifier = Modifier.padding(dimensionResource(R.dimen.major_100)) ) } - PurchasesSection( - total = "$120.99", + Divider() + MarkComplete( markOrderComplete = markOrderComplete, - onMarkOrderCompleteChange = onMarkOrderCompleteChange, - onPurchaseShippingLabel = onPurchaseShippingLabel + onMarkOrderCompleteChange = onMarkOrderCompleteChange ) } } From 66d07bb284a3080961d26c9a8e872dd4cca0c017 Mon Sep 17 00:00:00 2001 From: Alejo Date: Sun, 17 Nov 2024 10:58:24 -0600 Subject: [PATCH 49/80] hack purchase button position --- .../WooShippingLabelCreationScreen.kt | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt index f9957397635..850c213ef9f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.orders.wooshippinglabels import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -49,25 +50,48 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) ) } -@OptIn(ExperimentalMaterialApi::class) @Composable fun WooShippingLabelCreationScreen( modifier: Modifier = Modifier, onSelectPackageClick: () -> Unit, onPurchaseShippingLabel: () -> Unit +) { + Box(modifier = Modifier.fillMaxSize()) { + LabelCreationScreenWithBottomSheet( + modifier = modifier, + onSelectPackageClick = onSelectPackageClick, + onPurchaseShippingLabel = onPurchaseShippingLabel + ) + Box(modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter)) { + Surface(elevation = 4.dp) { + PurchaseButton(total = "$34.89", onPurchaseShippingLabel = { }) + } + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun LabelCreationScreenWithBottomSheet( + modifier: Modifier = Modifier, + onSelectPackageClick: () -> Unit, + onPurchaseShippingLabel: () -> Unit ) { val scaffoldState = rememberBottomSheetScaffoldState() BottomSheetScaffold( sheetContent = { val markOrderComplete = remember { mutableStateOf(false) } ShipmentDetails( + modifier = Modifier.padding(bottom = 74.dp), scaffoldState = scaffoldState, markOrderComplete = markOrderComplete.value, onMarkOrderCompleteChange = { markOrderComplete.value = it }, onPurchaseShippingLabel = onPurchaseShippingLabel ) }, - sheetPeekHeight = 64.dp, + sheetPeekHeight = 132.dp, scaffoldState = scaffoldState, topBar = { TopAppBar( From d966696d4950a4925952d2f6c5b27be7e803a32a Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 18 Nov 2024 09:29:39 +0530 Subject: [PATCH 50/80] Add string resource for content description for Variable product and variation items in the items list --- WooCommerce/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index f4c1529fe73..bea83eee5c0 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4231,6 +4231,7 @@ Remove %s from cart Product %s, Price %s Variable Product %s, Price %s + Variation %s, Price %s Product in cart %s, Price %s Cart Clear From b0a13ecd36fc629a044727bfc7fb1b0ac72854a1 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 18 Nov 2024 09:30:22 +0530 Subject: [PATCH 51/80] Display price for variation in the items list screen --- .../ui/woopos/home/items/WooPosItemsList.kt | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt index 63aadf7b29e..c349461bfd6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt @@ -48,6 +48,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosLazyCo import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox 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 import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -85,7 +86,7 @@ fun ItemsList( ) } - is WooPosItem.Variation -> { + is Variation -> { VariationItem( modifier = Modifier.animateItemPlacement(), item = product, @@ -116,7 +117,7 @@ private fun ProductItem( onItemClicked: (item: WooPosItem) -> Unit ) { val itemContentDescription = stringResource( - id = R.string.woopos_cart_item_content_description, + id = R.string.woopos_product_item_content_description, item.name, item.price ) @@ -130,7 +131,7 @@ private fun VariableProductItem( onItemClicked: (item: WooPosItem) -> Unit ) { val itemContentDescription = stringResource( - id = R.string.woopos_cart_item_content_description, + id = R.string.woopos_variable_product_item_content_description, item.name, item.price ) @@ -140,11 +141,11 @@ private fun VariableProductItem( @Composable private fun VariationItem( modifier: Modifier = Modifier, - item: WooPosItem.Variation, + item: Variation, onItemClicked: (item: WooPosItem) -> Unit ) { val itemContentDescription = stringResource( - id = R.string.woopos_cart_item_content_description, + id = R.string.woopos_variation_item_content_description, item.name, item.price ) @@ -201,7 +202,7 @@ private fun ProductInfo(item: WooPosItem) { when (item) { is SimpleProduct -> SimpleProductDetails(item = item) is VariableProduct -> VariableProductDetails(item = item) - is WooPosItem.Variation -> {} + is Variation -> VariationProductDetails(item = item) } } } @@ -211,7 +212,7 @@ private fun ProductImage(item: WooPosItem) { val imageUrl = when (item) { is SimpleProduct -> item.imageUrl is VariableProduct -> item.imageUrl - is WooPosItem.Variation -> { "" } + is Variation -> item.imageUrl } AsyncImage( @@ -246,6 +247,15 @@ private fun VariableProductDetails(item: VariableProduct) { ) } +@Composable +fun VariationProductDetails(item: Variation) { + Text( + text = item.price, + style = MaterialTheme.typography.h6, + fontWeight = FontWeight.Normal + ) +} + @Composable fun ItemsLoadingIndicator(itemsCount: Int = 10) { WooPosLazyColumn( From f3e650430c1d27076f07d84ebbeed7785993a6d8 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 18 Nov 2024 09:32:30 +0530 Subject: [PATCH 52/80] Format price before displaying for variations --- .../woopos/home/items/variations/WooPosVariationsViewModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 0cd183c6742..0ec0ddd68fa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import com.woocommerce.android.ui.woopos.common.data.WooPosGetProductById import com.woocommerce.android.ui.woopos.home.items.WooPosItem import com.woocommerce.android.ui.woopos.home.items.WooPosVariationsViewState +import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow @@ -20,6 +21,7 @@ import javax.inject.Inject class WooPosVariationsViewModel @Inject constructor( private val getProductById: WooPosGetProductById, private val variationsDataSource: WooPosVariationsDataSource, + private val priceFormat: WooPosFormatPrice, ) : ViewModel() { private val _viewState = @@ -62,7 +64,7 @@ class WooPosVariationsViewModel @Inject constructor( WooPosItem.Variation( id = it.remoteVariationId, name = it.getName(product), - price = it.priceWithCurrency ?: "", + price = priceFormat(it.price), imageUrl = it.image?.source ) }, From 1e03af75121e8e299b87331358dd35e56e77785b Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 18 Nov 2024 09:32:37 +0530 Subject: [PATCH 53/80] Fix failing tests --- .../WooPosVariationsViewModelTest.kt | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index d2e4fd0c7b1..111ee3ccf65 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -11,12 +11,14 @@ import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsD import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsUIEvents import com.woocommerce.android.ui.woopos.home.items.variations.WooPosVariationsViewModel import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import com.woocommerce.android.ui.woopos.util.format.WooPosFormatPrice import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat +import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.ArgumentMatchers.eq @@ -26,6 +28,7 @@ import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import java.math.BigDecimal import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -41,13 +44,16 @@ class WooPosVariationsViewModelTest { private val getProductById: WooPosGetProductById = mock() private val variationsDataSource: WooPosVariationsDataSource = mock() + private val priceFormat: WooPosFormatPrice = mock { + onBlocking { invoke(any()) }.thenReturn("$10.0") + } private lateinit var wooPosVariationsViewModel: WooPosVariationsViewModel @Test fun `given view model init, then loading state is displayed`() = runTest { whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) assertThat( @@ -61,7 +67,7 @@ class WooPosVariationsViewModelTest { fun `given view model init, then API call is made to fetch product`() = runTest { whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) verify(getProductById).invoke(1L) @@ -71,7 +77,7 @@ class WooPosVariationsViewModelTest { fun `given view model init, then API call is made to fetch variation`() = runTest { whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) verify(variationsDataSource).fetchVariations(eq(1L), any()) @@ -81,7 +87,7 @@ class WooPosVariationsViewModelTest { fun `given view model init, then API call is made to fetch variation with forceRefresh set to true`() = runTest { whenever(variationsDataSource.getVariationsFlow(1L)).thenReturn(emptyFlow()) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) verify(variationsDataSource).fetchVariations(1L, forceRefresh = true) @@ -103,7 +109,7 @@ class WooPosVariationsViewModelTest { ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) advanceUntilIdle() @@ -130,7 +136,7 @@ class WooPosVariationsViewModelTest { ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) advanceUntilIdle() @@ -158,7 +164,7 @@ class WooPosVariationsViewModelTest { whenever(getProductById.invoke(any())).thenReturn( ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.onUIEvent( @@ -184,7 +190,7 @@ class WooPosVariationsViewModelTest { whenever(getProductById.invoke(any())).thenReturn( ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) val states: MutableList = mutableListOf() wooPosVariationsViewModel.viewState.asLiveData().observeForever { states.add(it) @@ -208,7 +214,7 @@ class WooPosVariationsViewModelTest { ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.PullToRefreshTriggered(1L)) @@ -228,7 +234,7 @@ class WooPosVariationsViewModelTest { Result.failure(Throwable()) ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.viewState.test { @@ -247,7 +253,7 @@ class WooPosVariationsViewModelTest { whenever(variationsDataSource.fetchVariations(any(), any())).thenReturn( Result.failure(Throwable()) ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) val states: MutableList = mutableListOf() wooPosVariationsViewModel.viewState.asLiveData().observeForever { states.add(it) @@ -280,7 +286,7 @@ class WooPosVariationsViewModelTest { whenever(variationsDataSource.loadMore(any())).thenReturn( Result.failure(Throwable()) ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.onUIEvent(WooPosVariationsUIEvents.EndOfItemsListReached(1L)) @@ -297,7 +303,7 @@ class WooPosVariationsViewModelTest { Result.failure(Throwable()) ) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.loadMore(1L) @@ -315,7 +321,7 @@ class WooPosVariationsViewModelTest { ) whenever(variationsDataSource.canLoadMore()).thenReturn(false) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.loadMore(1L) @@ -342,7 +348,7 @@ class WooPosVariationsViewModelTest { ) whenever(variationsDataSource.canLoadMore()).thenReturn(true) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.loadMore(1L) @@ -372,7 +378,7 @@ class WooPosVariationsViewModelTest { ) whenever(variationsDataSource.canLoadMore()).thenReturn(true) - wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource) + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) wooPosVariationsViewModel.init(1L) wooPosVariationsViewModel.loadMore(1L) From 39d094be150e29c0128553794e616fb735871d13 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Mon, 18 Nov 2024 09:34:02 +0530 Subject: [PATCH 54/80] Remove unused imports --- .../ui/woopos/home/variations/WooPosVariationsViewModelTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index 111ee3ccf65..b26ced71d82 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat -import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.ArgumentMatchers.eq @@ -28,7 +27,6 @@ import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -import java.math.BigDecimal import kotlin.test.assertFalse import kotlin.test.assertTrue From e23a7966bb4708d3a4b902ec1f8ca37e62326817 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 18 Nov 2024 19:28:26 +0100 Subject: [PATCH 55/80] Create unit test file for JetpackActivationWPComEmailViewModel --- ...etpackActivationWPComEmailViewModelTest.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModelTest.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModelTest.kt new file mode 100644 index 00000000000..1df71d0ca41 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModelTest.kt @@ -0,0 +1,42 @@ +package com.woocommerce.android.ui.login.jetpack.wpcom + +import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.analytics.AnalyticsTrackerWrapper +import com.woocommerce.android.ui.login.WPComLoginRepository +import com.woocommerce.android.viewmodel.BaseUnitTest +import com.woocommerce.android.viewmodel.MultiLiveEvent +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Before +import org.junit.Test +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@ExperimentalCoroutinesApi +@OptIn(ExperimentalCoroutinesApi::class) +class JetpackActivationWPComEmailViewModelTest : BaseUnitTest() { + + private val savedStateHandle: SavedStateHandle = mock() + private val wpComLoginRepository: WPComLoginRepository = mock() + private val analyticsTrackerWrapper: AnalyticsTrackerWrapper = mock() + private lateinit var viewModel: JetpackActivationWPComEmailViewModel + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + viewModel = JetpackActivationWPComEmailViewModel( + savedStateHandle, + wpComLoginRepository, + analyticsTrackerWrapper + ) + } + + @Test + fun `when close is clicked, then trigger Exit event and clear access token`() = testBlocking { + viewModel.onCloseClick() + + assert(viewModel.event.value is MultiLiveEvent.Event.Exit) + verify(wpComLoginRepository).clearAccessToken() + } +} + From aacab0a2dfd2e81f6af39ce1d142ee898869a19f Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 18 Nov 2024 19:31:59 +0100 Subject: [PATCH 56/80] Remove feature flag to handle unit tests --- .../jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt | 5 +---- .../main/kotlin/com/woocommerce/android/util/FeatureFlag.kt | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt index 81c0c108d62..f86c3448d9e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt @@ -10,7 +10,6 @@ import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.analytics.AnalyticsTrackerWrapper import com.woocommerce.android.model.JetpackStatus import com.woocommerce.android.ui.login.WPComLoginRepository -import com.woocommerce.android.util.FeatureFlag import com.woocommerce.android.util.StringUtils import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit @@ -109,7 +108,7 @@ class JetpackActivationWPComEmailViewModel @Inject constructor( !StringUtils.isValidEmail(emailOrUsername) -> errorMessage.value = R.string.username_not_registered_wpcom - FeatureFlag.JETPACK_FLOW_ACCOUNT_CREATION.isEnabled() -> { + else -> { triggerEvent( ShowMagicLinkScreen( emailOrUsername, @@ -119,8 +118,6 @@ class JetpackActivationWPComEmailViewModel @Inject constructor( ) isSignup = true } - - else -> errorMessage.value = R.string.email_not_registered_wpcom } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt index 879bdda6439..200aebc1dd6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt @@ -19,8 +19,7 @@ enum class FeatureFlag { CUSTOM_FIELDS, REVAMP_WOO_SHIPPING, OBJECTIVE_SECTION, - POS_NON_SIMPLE_PRODUCT_TYPES, - JETPACK_FLOW_ACCOUNT_CREATION; + POS_NON_SIMPLE_PRODUCT_TYPES; fun isEnabled(context: Context? = null): Boolean { return when (this) { @@ -33,8 +32,7 @@ enum class FeatureFlag { ORDER_CREATION_AUTO_TAX_RATE, WOO_POS_PAYMENTS_ONBOARDING, REVAMP_WOO_SHIPPING, - POS_NON_SIMPLE_PRODUCT_TYPES, - JETPACK_FLOW_ACCOUNT_CREATION -> PackageUtils.isDebugBuild() + POS_NON_SIMPLE_PRODUCT_TYPES -> PackageUtils.isDebugBuild() NEW_SHIPPING_SUPPORT, INBOX, From b117cf74f25503ecbade712eb2286ebe007ed1c5 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 18 Nov 2024 19:32:25 +0100 Subject: [PATCH 57/80] Remove unused string --- WooCommerce/src/main/res/values/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 4cda5c08408..6f4c0d46315 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3429,7 +3429,6 @@ We just sent you a magic link to your e-mail address. Please tap the link in the email to log in. Log in with magic link - Hmm, we can\'t find a WordPress.com account connected to this email address. Hmm, we can\'t find a WordPress.com account connected to this username address. You can enter an email to create a new account. We\'ll email you a link that\'ll log you in instantly, no password needed. Get a login link by email From bba34cfa1853edd46c1f8fd6249de16e2358b99d Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 18 Nov 2024 21:32:26 +0100 Subject: [PATCH 58/80] Add String utils as dependency to allow mocking --- .../jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt index f86c3448d9e..9862b266a0d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModel.kt @@ -29,7 +29,8 @@ import javax.inject.Inject class JetpackActivationWPComEmailViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val wpComLoginRepository: WPComLoginRepository, - private val analyticsTrackerWrapper: AnalyticsTrackerWrapper + private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, + private val stringUtils: StringUtils, ) : ScopedViewModel(savedStateHandle) { private val navArgs: JetpackActivationWPComEmailFragmentArgs by savedStateHandle.navArgs() @@ -105,7 +106,7 @@ class JetpackActivationWPComEmailViewModel @Inject constructor( when (failure?.type) { AuthOptionsErrorType.UNKNOWN_USER -> { when { - !StringUtils.isValidEmail(emailOrUsername) -> + !stringUtils.isValidEmail(emailOrUsername) -> errorMessage.value = R.string.username_not_registered_wpcom else -> { From b857504646d04b506a59dc702a2d0067e91c8f56 Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Mon, 18 Nov 2024 21:51:21 +0100 Subject: [PATCH 59/80] Add unit tests to JetpackActivationWPComEmailViewModel --- ...etpackActivationWPComEmailViewModelTest.kt | 167 ++++++++++++++++-- 1 file changed, 152 insertions(+), 15 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModelTest.kt index 1df71d0ca41..f32b915a9cd 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/login/jetpack/wpcom/JetpackActivationWPComEmailViewModelTest.kt @@ -1,42 +1,179 @@ package com.woocommerce.android.ui.login.jetpack.wpcom -import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.OnChangedException +import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsTrackerWrapper +import com.woocommerce.android.model.JetpackStatus import com.woocommerce.android.ui.login.WPComLoginRepository +import com.woocommerce.android.ui.login.jetpack.wpcom.JetpackActivationWPComEmailViewModel.ShowMagicLinkScreen +import com.woocommerce.android.ui.login.jetpack.wpcom.JetpackActivationWPComEmailViewModel.ShowPasswordScreen +import com.woocommerce.android.util.StringUtils +import com.woocommerce.android.util.runAndCaptureValues import com.woocommerce.android.viewmodel.BaseUnitTest import com.woocommerce.android.viewmodel.MultiLiveEvent import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.Before -import org.junit.Test -import org.mockito.MockitoAnnotations +import org.assertj.core.api.Assertions.assertThat import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.store.AccountStore.AuthOptionsError +import org.wordpress.android.fluxc.store.AccountStore.AuthOptionsErrorType +import org.wordpress.android.login.AuthOptions +import kotlin.test.Test +import kotlin.test.assertTrue -@ExperimentalCoroutinesApi @OptIn(ExperimentalCoroutinesApi::class) class JetpackActivationWPComEmailViewModelTest : BaseUnitTest() { + companion object { + const val WPCOM_EMAIL = "wpcomUser@gmail.com" + const val UNKNOWN_EMAIL = "newUser@example.com" + const val UNKNOWN_USERNAME = "newUser" + val JETPACK_STATUS = JetpackStatus( + isJetpackInstalled = true, + isJetpackConnected = false, + wpComEmail = "" + ) + } - private val savedStateHandle: SavedStateHandle = mock() + private val saveStateHandle = JetpackActivationWPComEmailFragmentArgs( + jetpackStatus = JETPACK_STATUS, + ).toSavedStateHandle() private val wpComLoginRepository: WPComLoginRepository = mock() private val analyticsTrackerWrapper: AnalyticsTrackerWrapper = mock() + private val stringUtils: StringUtils = mock() private lateinit var viewModel: JetpackActivationWPComEmailViewModel - @Before - fun setUp() { - MockitoAnnotations.openMocks(this) + fun setup(wpComEmail: String) { + saveStateHandle["email"] = wpComEmail viewModel = JetpackActivationWPComEmailViewModel( - savedStateHandle, - wpComLoginRepository, - analyticsTrackerWrapper + savedStateHandle = saveStateHandle, + wpComLoginRepository = wpComLoginRepository, + analyticsTrackerWrapper = analyticsTrackerWrapper, + stringUtils = stringUtils ) } @Test fun `when close is clicked, then trigger Exit event and clear access token`() = testBlocking { - viewModel.onCloseClick() + setup(WPCOM_EMAIL) + val event = viewModel.event.runAndCaptureValues { + viewModel.onCloseClick() + }.last() - assert(viewModel.event.value is MultiLiveEvent.Event.Exit) + assertTrue(event is MultiLiveEvent.Event.Exit) verify(wpComLoginRepository).clearAccessToken() } -} + @Test + fun `given email is not WPcom, when onContinueClick clicked, then trigger ShowMagicLinkScreen with new wpcom account true`() = + testBlocking { + setup(UNKNOWN_EMAIL) + whenever(stringUtils.isValidEmail(UNKNOWN_EMAIL)).thenReturn(true) + whenever(wpComLoginRepository.fetchAuthOptions(UNKNOWN_EMAIL)) + .thenReturn( + Result.failure(OnChangedException(AuthOptionsError(AuthOptionsErrorType.UNKNOWN_USER, ""))) + ) + + val event = viewModel.event.runAndCaptureValues { + viewModel.onContinueClick() + }.last() + + assertThat(event).isEqualTo( + ShowMagicLinkScreen( + UNKNOWN_EMAIL, + JETPACK_STATUS, + isNewWpComAccount = true + ) + ) + } + + @Test + fun `given username is not WPcom, when onContinueClick clicked, then show user name not wpcom error`() = + testBlocking { + setup(UNKNOWN_USERNAME) + whenever(stringUtils.isValidEmail(UNKNOWN_USERNAME)).thenReturn(false) + whenever(wpComLoginRepository.fetchAuthOptions(UNKNOWN_USERNAME)).thenReturn( + Result.failure( + OnChangedException( + AuthOptionsError(AuthOptionsErrorType.UNKNOWN_USER, "") + ) + ) + ) + + val state = viewModel.viewState.runAndCaptureValues { + viewModel.onContinueClick() + }.last() + + assertThat(state.errorMessage).isEqualTo(R.string.username_not_registered_wpcom) + } + + @Test + fun `given email not allowed, when onContinueClick clicked, then show user name not wpcom error`() = + testBlocking { + setup(UNKNOWN_USERNAME) + whenever(wpComLoginRepository.fetchAuthOptions(UNKNOWN_USERNAME)).thenReturn( + Result.failure( + OnChangedException( + AuthOptionsError(AuthOptionsErrorType.EMAIL_LOGIN_NOT_ALLOWED, "") + ) + ) + ) + + val state = viewModel.viewState.runAndCaptureValues { + viewModel.onContinueClick() + }.last() + + assertThat(state.errorMessage).isEqualTo(R.string.error_user_username_instead_of_email) + } + + @Test + fun `given email is WPcom user, when onContinueClick clicked, then trigger ShowPasswordScreen`() = + testBlocking { + setup(WPCOM_EMAIL) + whenever(wpComLoginRepository.fetchAuthOptions(WPCOM_EMAIL)).thenReturn( + Result.success( + AuthOptions( + isPasswordless = false, + isEmailVerified = true + ) + ) + ) + + val event = viewModel.event.runAndCaptureValues { + viewModel.onContinueClick() + }.last() + + assertThat(event).isEqualTo( + ShowPasswordScreen( + WPCOM_EMAIL, + JETPACK_STATUS + ) + ) + } + + @Test + fun `given email is WPcom passwordless user, when onContinueClick clicked, then trigger ShowPasswordScreen`() = + testBlocking { + setup(WPCOM_EMAIL) + whenever(wpComLoginRepository.fetchAuthOptions(WPCOM_EMAIL)).thenReturn( + Result.success( + AuthOptions( + isPasswordless = true, + isEmailVerified = true + ) + ) + ) + + val event = viewModel.event.runAndCaptureValues { + viewModel.onContinueClick() + }.last() + + assertThat(event).isEqualTo( + ShowMagicLinkScreen( + WPCOM_EMAIL, + JETPACK_STATUS, + isNewWpComAccount = false + ) + ) + } +} From ae2256ba99421a440707f6d86fc1574dac25b25f Mon Sep 17 00:00:00 2001 From: jorgemucientesfayos Date: Tue, 19 Nov 2024 11:56:25 +0100 Subject: [PATCH 60/80] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 5a315da6d3d..5671e074cac 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -5,6 +5,7 @@ ----- - [*] Fixed shipping lines being editable at all states [https://github.com/woocommerce/woocommerce-android/pull/12890] - [*] Fixed a crash that occurred when tapping on the customer shipping address in the order details screen [https://github.com/woocommerce/woocommerce-android/pull/12920] +- [**] Enables creating a new WP.com account during Jetpack activation flow [https://github.com/woocommerce/woocommerce-android/issues/11114] 21.1 ----- From 4f0e7613f46f9889d73781d251793b55f92e4bbc Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 19 Nov 2024 15:59:00 +0100 Subject: [PATCH 61/80] Cancel payment flow is fetching of the receipt fails for printing --- .../ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt index 617c78d01a3..bcced34f402 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/cardreader/payment/CardReaderPaymentViewModel.kt @@ -744,6 +744,7 @@ class CardReaderPaymentViewModel errorDescription = receiptResult.exceptionOrNull()?.message ?: "Unknown error", ) triggerEvent(ShowSnackbar(R.string.receipt_fetching_error)) + onCancelPaymentFlow() } } } From bdefffaf8ffa43a881c37f3e06793600f6772283 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 19 Nov 2024 16:18:40 +0100 Subject: [PATCH 62/80] Unit test on exit after failed fetching of a receipt URL --- .../CardReaderPaymentViewModelTest.kt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt index dcab52ca408..9105dd18086 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/payments/cardreader/CardReaderPaymentViewModelTest.kt @@ -2314,12 +2314,14 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { whenever(paymentReceiptHelper.getReceiptUrl(any())).thenReturn(Result.failure(Exception())) // WHEN + val events = viewModel.event.captureValues() viewModel.start() (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onPrimaryActionClicked.invoke() // THEN - assertThat((viewModel.event.value as ShowSnackbar).message).isEqualTo(R.string.receipt_fetching_error) + assertThat((events[events.size - 2] as ShowSnackbar).message).isEqualTo(R.string.receipt_fetching_error) + assertThat(events.last()).isInstanceOf(Exit::class.java) } @Test @@ -4479,6 +4481,21 @@ class CardReaderPaymentViewModelTest : BaseUnitTest() { } } + @Test + fun `given fetching receipt URL fails, when startPrintingFlow, then payment flow is canceled`() = testBlocking { + val errorMessage = "Receipt fetching error" + whenever(paymentReceiptHelper.getReceiptUrl(any())).thenReturn(Result.failure(Exception(errorMessage))) + whenever(cardReaderManager.collectPayment(any())).thenAnswer { + flow { emit(PaymentCompleted("")) } + } + viewModel.start() + + (viewModel.viewStateData.value as ExternalReaderPaymentSuccessfulState).onPrimaryActionClicked.invoke() + + verify(tracker).trackReceiptUrlFetchingFails(errorDescription = errorMessage) + assertThat(viewModel.event.value).isInstanceOf(Exit::class.java) + } + private suspend fun simulateFetchOrderJobState(inProgress: Boolean) { if (inProgress) { whenever(orderRepository.fetchOrderById(any())).doSuspendableAnswer { From f5afca4e9ccb4a9ba794be89ed07563fc641f783 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 19 Nov 2024 16:31:26 +0100 Subject: [PATCH 63/80] Updated release notes --- RELEASE-NOTES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index a0373887201..44170352d21 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,12 +1,13 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] *** Use [*****] to indicate smoke tests of all critical flows should be run on the final APK before release (e.g. major library or targetSdk updates). *** For entries which are touching the Android Wear app's, start entry with `[WEAR]` too. +----- 21.2 - [Internal] Changed a way how authenticated web view opened in the IPP flows [https://github.com/woocommerce/woocommerce-android/pull/12908] ------ - [**][Payments] Fixed a bug when IPP onboarding was not possible to finish from the app [https://github.com/woocommerce/woocommerce-android/pull/12917] - [*] Fixed shipping lines being editable at all states [https://github.com/woocommerce/woocommerce-android/pull/12890] - [*] Fixed a crash that occurred when tapping on the customer shipping address in the order details screen [https://github.com/woocommerce/woocommerce-android/pull/12920] +- [*][Payments] Fixed a bug when loading indicator was shown forever after fetching of a receipt failed [https://github.com/woocommerce/woocommerce-android/pull/12950] 21.1 ----- From 1f6bd247cf0f7647de6f07185fec489cd4e07ae8 Mon Sep 17 00:00:00 2001 From: Alejo Date: Tue, 19 Nov 2024 17:14:00 -0600 Subject: [PATCH 64/80] landscape with mark order complete --- .../wooshippinglabels/PurchaseSection.kt | 1 - .../wooshippinglabels/ShipmentDetails.kt | 23 ++----------- .../WooShippingLabelCreationScreen.kt | 34 +++++++++++++------ 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt index e621a00711b..27c32aa0640 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt @@ -44,7 +44,6 @@ internal fun PurchasesSectionLandscape( modifier: Modifier = Modifier ) { Column(modifier.fillMaxWidth()) { - Divider() Row(verticalAlignment = Alignment.CenterVertically) { MarkComplete( markOrderComplete = markOrderComplete, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt index add97abe02b..96f31b1ad34 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt @@ -55,7 +55,6 @@ fun ShipmentDetails( scaffoldState: BottomSheetScaffoldState, markOrderComplete: Boolean, onMarkOrderCompleteChange: (Boolean) -> Unit, - onPurchaseShippingLabel: () -> Unit, modifier: Modifier = Modifier, handlerModifier: Modifier = Modifier, ) { @@ -91,12 +90,7 @@ fun ShipmentDetails( } } if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) { - ShipmentDetailsLandscape( - modifier = modifier, - markOrderComplete = markOrderComplete, - onMarkOrderCompleteChange = onMarkOrderCompleteChange, - onPurchaseShippingLabel = onPurchaseShippingLabel - ) + ShipmentDetailsLandscape(modifier = modifier) } else { ShipmentDetailsPortrait( modifier = modifier, @@ -145,9 +139,6 @@ private fun ShipmentDetailsPortrait( @Composable private fun ShipmentDetailsLandscape( - markOrderComplete: Boolean, - onMarkOrderCompleteChange: (Boolean) -> Unit, - onPurchaseShippingLabel: () -> Unit, modifier: Modifier = Modifier, ) { Column(modifier) { @@ -185,12 +176,6 @@ private fun ShipmentDetailsLandscape( ) } } - PurchasesSectionLandscape( - total = "$120.99", - markOrderComplete = markOrderComplete, - onMarkOrderCompleteChange = onMarkOrderCompleteChange, - onPurchaseShippingLabel = onPurchaseShippingLabel - ) } } @@ -273,11 +258,7 @@ private fun OrderDetailsSectionLandscape( fun ShipmentDetailsLandscapePreview() { WooThemeWithBackground { Surface { - ShipmentDetailsLandscape( - markOrderComplete = false, - onMarkOrderCompleteChange = {}, - onPurchaseShippingLabel = {} - ) + ShipmentDetailsLandscape() } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt index 850c213ef9f..c78c5874435 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/WooShippingLabelCreationScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.BottomSheetScaffold +import androidx.compose.material.BottomSheetScaffoldState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton @@ -28,6 +29,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -50,23 +52,37 @@ fun WooShippingLabelCreationScreen(viewModel: WooShippingLabelCreationViewModel) ) } +@OptIn(ExperimentalMaterialApi::class) @Composable fun WooShippingLabelCreationScreen( modifier: Modifier = Modifier, onSelectPackageClick: () -> Unit, onPurchaseShippingLabel: () -> Unit ) { + val scaffoldState = rememberBottomSheetScaffoldState() Box(modifier = Modifier.fillMaxSize()) { LabelCreationScreenWithBottomSheet( modifier = modifier, onSelectPackageClick = onSelectPackageClick, - onPurchaseShippingLabel = onPurchaseShippingLabel + scaffoldState = scaffoldState ) - Box(modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter)) { - Surface(elevation = 4.dp) { - PurchaseButton(total = "$34.89", onPurchaseShippingLabel = { }) + val elevation = if (scaffoldState.bottomSheetState.isCollapsed) { 0.dp } else { 4.dp } + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + ) { + Surface(elevation = elevation) { + if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) { + PurchasesSectionLandscape( + total = "$34.89", + markOrderComplete = true, + onMarkOrderCompleteChange = { }, + onPurchaseShippingLabel = onPurchaseShippingLabel + ) + } else { + PurchaseButton(total = "$34.89", onPurchaseShippingLabel = { }) + } } } } @@ -77,9 +93,8 @@ fun WooShippingLabelCreationScreen( private fun LabelCreationScreenWithBottomSheet( modifier: Modifier = Modifier, onSelectPackageClick: () -> Unit, - onPurchaseShippingLabel: () -> Unit + scaffoldState: BottomSheetScaffoldState ) { - val scaffoldState = rememberBottomSheetScaffoldState() BottomSheetScaffold( sheetContent = { val markOrderComplete = remember { mutableStateOf(false) } @@ -87,8 +102,7 @@ private fun LabelCreationScreenWithBottomSheet( modifier = Modifier.padding(bottom = 74.dp), scaffoldState = scaffoldState, markOrderComplete = markOrderComplete.value, - onMarkOrderCompleteChange = { markOrderComplete.value = it }, - onPurchaseShippingLabel = onPurchaseShippingLabel + onMarkOrderCompleteChange = { markOrderComplete.value = it } ) }, sheetPeekHeight = 132.dp, From 290637fd7f4576625a453bc492fe8b89a9ac55e8 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Wed, 20 Nov 2024 09:51:49 +0530 Subject: [PATCH 65/80] Fix merge conflicts --- .../woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt | 2 +- .../android/ui/woopos/home/items/WooPosItemsScreen.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt index c349461bfd6..f0a9be434fd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt @@ -56,10 +56,10 @@ import kotlinx.coroutines.flow.filter @Composable fun ItemsList( state: ContentViewState, + listState: LazyListState, onItemClicked: (item: WooPosItem) -> Unit, onEndOfProductsListReached: () -> Unit, ) { - val listState = rememberLazyListState() WooPosLazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(2.dp), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt index 3a7e8fc18ca..39cf2027df5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt @@ -15,6 +15,8 @@ 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.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.ContentAlpha From 786f703da03b016444fc0f51b1b061e77adf1f88 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Wed, 20 Nov 2024 09:53:19 +0530 Subject: [PATCH 66/80] Remove unused import --- .../woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt index f0a9be434fd..c4447c64e20 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt @@ -17,7 +17,6 @@ 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.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Text From 363647e124574b149528c6635fe77731bab89135 Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Wed, 20 Nov 2024 11:21:53 +0530 Subject: [PATCH 67/80] pass list state from variations screen --- .../woopos/home/items/variations/WooPosVariationsScreen.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index 2d9bb08425c..284915a1515 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton @@ -156,7 +157,11 @@ private fun WooPosVariationsScreens( when (val itemsState = itemState.value) { is WooPosVariationsViewState.Content -> { Spacer(modifier = Modifier.height(16.dp)) - ItemsList(state = itemsState, onItemClicked = {}) { + ItemsList( + state = itemsState, + listState = rememberLazyListState(), + onItemClicked = {} + ) { onEndOfItemListReached() } } From 14d9e4c2947418854b1f2d920ff2b037e4ef792a Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Wed, 20 Nov 2024 13:14:33 +0530 Subject: [PATCH 68/80] filter out variations with price set to null and allow products whose price is set to 0 --- .../products/WooPosProductsDataSource.kt | 3 +-- .../variations/WooPosVariationsViewModel.kt | 17 ++++++------ .../android/ui/products/ProductTestUtils.kt | 5 ++-- .../WooPosVariationsViewModelTest.kt | 27 +++++++++++++++++++ 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsDataSource.kt index b6aa349f827..fab98f0df85 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsDataSource.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.wordpress.android.fluxc.store.WCProductStore -import java.math.BigDecimal import javax.inject.Inject import javax.inject.Singleton @@ -100,7 +99,7 @@ class WooPosProductsDataSource @Inject constructor( private fun isProductNotVirtual(product: Product) = !product.isVirtual private fun isProductHasAPrice(product: Product) = - (product.price != null && product.price.compareTo(BigDecimal.ZERO) != 0) + (product.price != null) sealed class ProductsResult { data class Cached(val products: List) : ProductsResult() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt index 0ec0ddd68fa..c46feeba98f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsViewModel.kt @@ -60,14 +60,15 @@ class WooPosVariationsViewModel @Inject constructor( if (result.isSuccess) { variationsDataSource.getVariationsFlow(productId).collect { variationList -> _viewState.value = WooPosVariationsViewState.Content( - items = variationList.map { - WooPosItem.Variation( - id = it.remoteVariationId, - name = it.getName(product), - price = priceFormat(it.price), - imageUrl = it.image?.source - ) - }, + items = variationList.filter { it.price != null } + .map { + WooPosItem.Variation( + id = it.remoteVariationId, + name = it.getName(product), + price = priceFormat(it.price), + imageUrl = it.image?.source + ) + }, loadingMore = false, reloadingProductsWithPullToRefresh = false, ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt index 12232be7148..ad18e4a54fe 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt @@ -116,14 +116,15 @@ object ProductTestUtils { fun generateProductVariation( productId: Long = 1L, - variationId: Long = 1L + variationId: Long = 1L, + amount: String = "10.00", ): ProductVariation { return WCProductVariationModel(2).apply { dateCreated = "2018-01-05T05:14:30Z" localSiteId = 1 remoteProductId = productId remoteVariationId = variationId - price = "10.00" + price = amount image = "" attributes = "" }.toAppModel().also { it.priceWithCurrency = "$10.00" } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt index b26ced71d82..0dcc7df5cd9 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/variations/WooPosVariationsViewModelTest.kt @@ -118,6 +118,33 @@ class WooPosVariationsViewModelTest { } } + @Test + fun `given view model init, when variation fetched successfully, then filter out variations with price null`() = + runTest { + whenever(variationsDataSource.getVariationsFlow(any())).thenReturn( + flowOf( + listOf( + ProductTestUtils.generateProductVariation(1L, 2L, amount = ""), + ProductTestUtils.generateProductVariation(1L, 3L), + ProductTestUtils.generateProductVariation(1L, 4L), + ) + ) + ) + whenever(getProductById.invoke(any())).thenReturn( + ProductTestUtils.generateProduct(1L, isVariable = true, productType = "variable") + ) + + wooPosVariationsViewModel = WooPosVariationsViewModel(getProductById, variationsDataSource, priceFormat) + wooPosVariationsViewModel.init(1L) + advanceUntilIdle() + + wooPosVariationsViewModel.viewState.test { + // THEN + val value = awaitItem() as WooPosVariationsViewState.Content + assertThat(value.items.size).isEqualTo(2) + } + } + @Test fun `given view model init, when variation fetched successfully, then view state is updated with proper variation content`() = runTest { From 9a657e9063df3fc094bf3961a05e3bb6b1d0aaf7 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 20 Nov 2024 10:38:15 +0100 Subject: [PATCH 69/80] Close SelectPaymentMethodFragment.kt when we skip it in the WooPOS flow --- .../payments/methodselection/SelectPaymentMethodFragment.kt | 2 +- .../src/main/res/navigation/nav_graph_payment_flow.xml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt index 7731e34a0ef..2466e03fd3e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/payments/methodselection/SelectPaymentMethodFragment.kt @@ -213,7 +213,7 @@ class SelectPaymentMethodFragment : BaseFragment(R.layout.fragment_select_paymen if (findNavController().currentDestination?.id == R.id.selectPaymentMethodFragment) { findNavController().navigate( SelectPaymentMethodFragmentDirections - .actionSelectPaymentMethodFragmentToCardReaderPaymentFlow( + .actionWooPosSelectPaymentMethodFragmentToCardReaderPaymentFlow( event.cardReaderFlowParam, event.cardReaderType ) diff --git a/WooCommerce/src/main/res/navigation/nav_graph_payment_flow.xml b/WooCommerce/src/main/res/navigation/nav_graph_payment_flow.xml index f9693197631..cfea09cc22a 100644 --- a/WooCommerce/src/main/res/navigation/nav_graph_payment_flow.xml +++ b/WooCommerce/src/main/res/navigation/nav_graph_payment_flow.xml @@ -21,6 +21,11 @@ + Date: Wed, 20 Nov 2024 15:55:44 +0530 Subject: [PATCH 70/80] Make the tests pass --- .../ui/woopos/home/items/WooPosProductsDataSourceTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosProductsDataSourceTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosProductsDataSourceTest.kt index ac67765fcfb..3325a7e7693 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosProductsDataSourceTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosProductsDataSourceTest.kt @@ -309,7 +309,7 @@ class WooPosProductsDataSourceTest { ProductTestUtils.generateProduct( productId = 1, productName = "Product 1", - amount = "0", + amount = "", productType = "simple", isDownloadable = false, ), From 5ab8a26efd7562cae31a01d2966fdc61971c89e3 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 20 Nov 2024 11:34:47 +0100 Subject: [PATCH 71/80] Read enableLeakCanary from global gradle properties --- WooCommerce/build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index a0da2ac7791..5684bf1f51b 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -567,9 +567,7 @@ static def loadPropertiesFromFile(inputFile) { } def isLeakCanaryEnabled() { - return project.properties.getOrDefault('enableLeakCanary', 'true') == 'true' + return gradle.hasProperty('enableLeakCanary') && gradle.enableLeakCanary == true } - - apply from: '../config/gradle/build_optimization.gradle' From 795879d0cf1e7783e49581a09dce185cccbdaf42 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 20 Nov 2024 13:41:17 +0100 Subject: [PATCH 72/80] Use `developer.properties` to enable/disable leak canary --- WooCommerce/build.gradle | 4 ++-- developer.properties-example | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 5684bf1f51b..ac645d24234 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -125,7 +125,7 @@ android { // TODO remove this once the hilt migration is complete javaCompileOptions.annotationProcessorOptions.arguments['dagger.hilt.disableModulesHaveInstallInCheck'] = 'true' - resValue "bool", "enable_leak_canary", isLeakCanaryEnabled().toString() + resValue "bool", "enable_leak_canary", isLeakCanaryEnabled() packagingOptions { exclude 'META-INF/gradle/incremental.annotation.processors' @@ -567,7 +567,7 @@ static def loadPropertiesFromFile(inputFile) { } def isLeakCanaryEnabled() { - return gradle.hasProperty('enableLeakCanary') && gradle.enableLeakCanary == true + return developerProperties.get("enable_leak_canary") } apply from: '../config/gradle/build_optimization.gradle' diff --git a/developer.properties-example b/developer.properties-example index 176be6b0dd0..788a73838f2 100644 --- a/developer.properties-example +++ b/developer.properties-example @@ -2,3 +2,4 @@ wc.in_app_update_type = 0 wc.iap_testing_sandbox_url= wc.jitm_testing_json_file_name= +enable_leak_canary=false From d1edcbb505f55074fee6eb9d19bc14bd068fd2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Wed, 20 Nov 2024 14:16:44 +0100 Subject: [PATCH 73/80] Add Feature Flag for the Product Global Unique Identifier support project --- .../main/kotlin/com/woocommerce/android/util/FeatureFlag.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt index 200aebc1dd6..fa05ba6b180 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt @@ -19,7 +19,8 @@ enum class FeatureFlag { CUSTOM_FIELDS, REVAMP_WOO_SHIPPING, OBJECTIVE_SECTION, - POS_NON_SIMPLE_PRODUCT_TYPES; + POS_NON_SIMPLE_PRODUCT_TYPES, + PRODUCT_GLOBAL_UNIQUE_IDENTIFIER_SUPPORT; fun isEnabled(context: Context? = null): Boolean { return when (this) { @@ -32,7 +33,8 @@ enum class FeatureFlag { ORDER_CREATION_AUTO_TAX_RATE, WOO_POS_PAYMENTS_ONBOARDING, REVAMP_WOO_SHIPPING, - POS_NON_SIMPLE_PRODUCT_TYPES -> PackageUtils.isDebugBuild() + POS_NON_SIMPLE_PRODUCT_TYPES, + PRODUCT_GLOBAL_UNIQUE_IDENTIFIER_SUPPORT -> PackageUtils.isDebugBuild() NEW_SHIPPING_SUPPORT, INBOX, From c312312aba2ab8d7d90e6b27648a9a07b4f9dd6e Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 20 Nov 2024 16:02:54 +0100 Subject: [PATCH 74/80] Changed default propertly to true --- developer.properties-example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer.properties-example b/developer.properties-example index 788a73838f2..dd12a2ecefd 100644 --- a/developer.properties-example +++ b/developer.properties-example @@ -2,4 +2,4 @@ wc.in_app_update_type = 0 wc.iap_testing_sandbox_url= wc.jitm_testing_json_file_name= -enable_leak_canary=false +enable_leak_canary=true From efc9a5c87851bae7d8f320994f4846e776e367ab Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 20 Nov 2024 16:05:06 +0100 Subject: [PATCH 75/80] Default to true --- WooCommerce/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index ac645d24234..674fbe8e060 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -567,7 +567,7 @@ static def loadPropertiesFromFile(inputFile) { } def isLeakCanaryEnabled() { - return developerProperties.get("enable_leak_canary") + return developerProperties.get("enable_leak_canary") ?: true } apply from: '../config/gradle/build_optimization.gradle' From 19797bd86d367c31b4e07e52188766fdeb2a1d76 Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 20 Nov 2024 16:06:55 +0100 Subject: [PATCH 76/80] Boolean to string --- WooCommerce/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/build.gradle b/WooCommerce/build.gradle index 674fbe8e060..90aa0fbff66 100644 --- a/WooCommerce/build.gradle +++ b/WooCommerce/build.gradle @@ -125,7 +125,7 @@ android { // TODO remove this once the hilt migration is complete javaCompileOptions.annotationProcessorOptions.arguments['dagger.hilt.disableModulesHaveInstallInCheck'] = 'true' - resValue "bool", "enable_leak_canary", isLeakCanaryEnabled() + resValue "bool", "enable_leak_canary", isLeakCanaryEnabled().toString() packagingOptions { exclude 'META-INF/gradle/incremental.annotation.processors' From e24f0eedff9abdddc25f01fb3c8f6f06b70bf6e9 Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 20 Nov 2024 10:20:00 -0600 Subject: [PATCH 77/80] remove unused import --- .../android/ui/orders/wooshippinglabels/PurchaseSection.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt index 27c32aa0640..4aff5ebb992 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/PurchaseSection.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.Divider import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment From 97c60214f34b9a39c152c23a1610e65622354e5d Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 20 Nov 2024 10:20:25 -0600 Subject: [PATCH 78/80] update bottom sheet icon --- .../ui/orders/wooshippinglabels/ShipmentDetails.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt index 96f31b1ad34..9e5d71554c5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/wooshippinglabels/ShipmentDetails.kt @@ -44,7 +44,6 @@ import androidx.compose.ui.unit.dp import com.woocommerce.android.R import com.woocommerce.android.model.Address import com.woocommerce.android.ui.compose.animations.SkeletonView -import com.woocommerce.android.ui.compose.component.BottomSheetHandle import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground import com.woocommerce.android.util.StringUtils import kotlinx.coroutines.launch @@ -79,7 +78,16 @@ fun ShipmentDetails( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - BottomSheetHandle(modifier = Modifier.padding(top = dimensionResource(R.dimen.minor_100))) + Icon( + modifier = Modifier.padding(top = dimensionResource(R.dimen.minor_100)), + painter = if (scaffoldState.bottomSheetState.isExpanded) { + painterResource(R.drawable.ic_arrow_down_26) + } else { + painterResource(R.drawable.ic_arrow_up_26) + }, + contentDescription = stringResource(R.string.order_creation_expand_collapse_order_totals), + tint = colorResource(id = R.color.color_primary), + ) AnimatedVisibility(visible = scaffoldState.bottomSheetState.isCollapsed) { Text( text = stringResource(R.string.shipping_label_shipment_details_title), From 0c2943a729dc96741693cf4c19ba8d3fbd398a15 Mon Sep 17 00:00:00 2001 From: Alejo Date: Wed, 20 Nov 2024 16:38:46 -0600 Subject: [PATCH 79/80] update mark complete text --- WooCommerce/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index f3cc7710ea4..df51b321d2e 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -1268,7 +1268,7 @@ Shipment details Order details Shipment cost - Mark this order as complete and notify the customer + After purchasing a label, mark this order as complete and notify the customer. Purchase Label ยท %1$s Purchase Label Cheapest From 4e26906a25bb758c236c6d4896526cd4e6afecec Mon Sep 17 00:00:00 2001 From: AnirudhBhat Date: Thu, 21 Nov 2024 10:01:11 +0530 Subject: [PATCH 80/80] Change the name from ItemsList to ItemList --- .../android/ui/woopos/home/items/WooPosItemsList.kt | 2 +- .../android/ui/woopos/home/items/WooPosItemsScreen.kt | 2 +- .../ui/woopos/home/items/variations/WooPosVariationsScreen.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt index c4447c64e20..176fb2aa9d0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsList.kt @@ -53,7 +53,7 @@ import kotlinx.coroutines.flow.filter @OptIn(ExperimentalFoundationApi::class) @Composable -fun ItemsList( +fun ItemList( state: ContentViewState, listState: LazyListState, onItemClicked: (item: WooPosItem) -> Unit, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt index 39cf2027df5..132b50dc37e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsScreen.kt @@ -164,7 +164,7 @@ private fun MainItemsList( onSimpleProductsBannerLearnMoreClicked, onSimpleProductsBannerClosed ) - ItemsList( + ItemList( itemsState, listState, onItemClicked, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt index 284915a1515..08e592ed55f 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/variations/WooPosVariationsScreen.kt @@ -46,7 +46,7 @@ import com.woocommerce.android.ui.woopos.common.composeui.WooPosTheme 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.toAdaptivePadding -import com.woocommerce.android.ui.woopos.home.items.ItemsList +import com.woocommerce.android.ui.woopos.home.items.ItemList 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.WooPosItemNavigationData.VariableProductData @@ -157,7 +157,7 @@ private fun WooPosVariationsScreens( when (val itemsState = itemState.value) { is WooPosVariationsViewState.Content -> { Spacer(modifier = Modifier.height(16.dp)) - ItemsList( + ItemList( state = itemsState, listState = rememberLazyListState(), onItemClicked = {}