From c905009bae2be99472f13f43d3666f1f2362e3d5 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 17 Oct 2025 08:49:15 +0200 Subject: [PATCH 1/9] Show error dialog in case of failed sync --- .../woopos/settings/WooPosSettingsScreen.kt | 9 ++ .../ui/woopos/settings/WooPosSettingsState.kt | 3 + .../settings/WooPosSettingsViewModel.kt | 6 ++ .../details/WooPosSettingsDetailPaneScreen.kt | 5 +- .../WooPosSettingsLocalCatalogScreen.kt | 92 +++++++++++++++++++ .../WooPosSettingsLocalCatalogViewModel.kt | 8 +- WooCommerce/src/main/res/values/strings.xml | 4 + 7 files changed, 125 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt index b7506dbefad5..fc454dbcf067 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt @@ -22,6 +22,7 @@ import com.woocommerce.android.ui.woopos.scanningsetup.WooPosScanningSetupDialog import com.woocommerce.android.ui.woopos.settings.categories.WooPosSettingsCategoriesPaneScreen import com.woocommerce.android.ui.woopos.settings.categories.WooPosSettingsCategory import com.woocommerce.android.ui.woopos.settings.details.WooPosSettingsDetailPaneScreen +import com.woocommerce.android.ui.woopos.settings.details.localcatalog.WooPosSyncErrorDialog import com.woocommerce.android.ui.woopos.settings.productinfo.WooPosSettingsProductInfoDialog import com.woocommerce.android.ui.woopos.settings.productinfo.WooPosSettingsProductInfoDialogState @@ -49,6 +50,7 @@ fun WooPosSettingsScreen(onNavigationEvent: (WooPosNavigationEvent) -> Unit) { onBack = containerViewModel::navigateBack, onShowProductInfoDialog = containerViewModel::showProductInfoDialog, onShowScanningSetupDialog = containerViewModel::showScanningSetupDialog, + onShowSyncErrorDialog = containerViewModel::showSyncErrorDialog, onDismissDialog = containerViewModel::hideDialog ) } @@ -62,6 +64,7 @@ private fun WooPosSettingsContent( onBack: () -> Unit, onShowProductInfoDialog: () -> Unit, onShowScanningSetupDialog: () -> Unit, + onShowSyncErrorDialog: (String) -> Unit, onDismissDialog: () -> Unit ) { Row( @@ -90,6 +93,7 @@ private fun WooPosSettingsContent( onBack = onBack, onShowProductInfoDialog = onShowProductInfoDialog, onShowScanningSetupDialog = onShowScanningSetupDialog, + onShowSyncErrorDialog = onShowSyncErrorDialog, modifier = Modifier .weight(0.7f) .background(MaterialTheme.colorScheme.surfaceContainerLow) @@ -107,6 +111,11 @@ private fun WooPosSettingsContent( isVisible = dialogState is WooPosSettingsDialogState.ScanningSetupDialog, onDismissRequest = onDismissDialog ) + + WooPosSyncErrorDialog( + isVisible = dialogState is WooPosSettingsDialogState.SyncErrorDialog, + onDismissRequest = onDismissDialog + ) } @WooPosPreview diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsState.kt index 11d0fcc963db..24d3d60bd54d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsState.kt @@ -76,4 +76,7 @@ sealed class WooPosSettingsDialogState : Parcelable { @Parcelize data object ScanningSetupDialog : WooPosSettingsDialogState() + + @Parcelize + data class SyncErrorDialog(val errorMessage: String) : WooPosSettingsDialogState() } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt index 8027b4647bc3..26578cf9a410 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt @@ -80,6 +80,12 @@ class WooPosSettingsViewModel @Inject constructor( } } + fun showSyncErrorDialog(errorMessage: String) { + _state.update { currentState -> + currentState.copy(dialogState = WooPosSettingsDialogState.SyncErrorDialog(errorMessage)) + } + } + fun hideDialog() { _state.update { currentState -> currentState.copy(dialogState = WooPosSettingsDialogState.Hidden) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/WooPosSettingsDetailPaneScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/WooPosSettingsDetailPaneScreen.kt index 9262c358e9d1..28511d46c200 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/WooPosSettingsDetailPaneScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/WooPosSettingsDetailPaneScreen.kt @@ -33,6 +33,7 @@ fun WooPosSettingsDetailPaneScreen( onBack: () -> Unit, onShowProductInfoDialog: () -> Unit, onShowScanningSetupDialog: () -> Unit, + onShowSyncErrorDialog: (String) -> Unit, modifier: Modifier = Modifier ) { val currentDestination = state.currentDestination @@ -95,7 +96,9 @@ fun WooPosSettingsDetailPaneScreen( } is WooPosSettingsDetailDestination.LocalCatalog.Overview -> { - WooPosSettingsLocalCatalogScreen() + WooPosSettingsLocalCatalogScreen( + onShowSyncErrorDialog = onShowSyncErrorDialog + ) } is WooPosSettingsDetailDestination.Help.Overview -> { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt index 2b21a8a80f0e..1e829f081094 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt @@ -9,10 +9,15 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults @@ -30,20 +35,25 @@ import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButtonState +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosDialogWrapper import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosText import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosCornerRadius import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTypography +import com.woocommerce.android.ui.woopos.common.composeui.designsystem.toAdaptivePadding @Composable fun WooPosSettingsLocalCatalogScreen( + onShowSyncErrorDialog: (String) -> Unit, modifier: Modifier = Modifier, viewModel: WooPosSettingsLocalCatalogViewModel = hiltViewModel() ) { val state by viewModel.state.collectAsState() + viewModel.setOnShowSyncErrorDialog(onShowSyncErrorDialog) + WooPosSettingsLocalCatalogScreen( state = state, onToggleCellularData = viewModel::toggleCellularDataUpdate, @@ -274,6 +284,77 @@ private fun SectionTitle(title: String) { ) } +@Composable +fun WooPosSyncErrorDialog( + isVisible: Boolean, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier +) { + WooPosDialogWrapper( + modifier = modifier, + isVisible = isVisible, + dialogBackgroundContentDescription = stringResource( + id = R.string.woopos_settings_local_catalog_sync_error_dialog_background_content_description + ), + onDismissRequest = onDismissRequest + ) { + Column( + modifier = Modifier + .background(color = MaterialTheme.colorScheme.surfaceBright) + .padding(WooPosSpacing.XLarge.value.toAdaptivePadding()) + ) { + Row { + Spacer(modifier = Modifier.weight(1f)) + IconButton( + onClick = onDismissRequest, + modifier = Modifier + ) { + Icon( + Icons.Default.Close, + contentDescription = stringResource( + id = R.string.woopos_exit_dialog_confirmation_close_content_description + ), + modifier = Modifier.size(40.dp), + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + } + } + + Spacer(modifier = Modifier.size(WooPosSpacing.XLarge.value.toAdaptivePadding())) + + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + WooPosText( + text = stringResource(R.string.woopos_settings_local_catalog_sync_error_dialog_title), + style = WooPosTypography.Heading, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + + Spacer(modifier = Modifier.height(WooPosSpacing.Medium.value.toAdaptivePadding())) + + WooPosText( + text = stringResource(R.string.woopos_settings_local_catalog_sync_error_dialog_message), + style = WooPosTypography.BodyLarge, + color = MaterialTheme.colorScheme.onSurface + ) + + Spacer(modifier = Modifier.height(WooPosSpacing.XLarge.value.toAdaptivePadding())) + + WooPosButton( + modifier = Modifier.fillMaxWidth(), + onClick = onDismissRequest, + text = stringResource(R.string.woopos_settings_local_catalog_sync_error_dialog_button) + ) + } + } + } +} + @WooPosPreview @Composable fun WooPosSettingsLocalCatalogScreenPreview() { @@ -322,3 +403,14 @@ fun WooPosSettingsLocalCatalogRefreshingPreview() { ) } } + +@WooPosPreview +@Composable +fun WooPosSyncErrorDialogPreview() { + WooPosTheme { + WooPosSyncErrorDialog( + isVisible = true, + onDismissRequest = {} + ) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt index f9d1bea63fde..abdcd14f939e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt @@ -29,12 +29,18 @@ class WooPosSettingsLocalCatalogViewModel @Inject constructor( private val _state = MutableStateFlow(WooPosSettingsLocalCatalogState()) val state: StateFlow = _state.asStateFlow() + private var onShowSyncErrorDialog: ((String) -> Unit)? = null + init { loadCatalogStatus() listenToCellularDataUpdateValue() } + fun setOnShowSyncErrorDialog(callback: (String) -> Unit) { + onShowSyncErrorDialog = callback + } + private fun loadCatalogStatus() { viewModelScope.launch { _state.update { it.copy(catalogStatus = WooPosSettingsLocalCatalogState.CatalogStatus.LoadingStatus) } @@ -90,8 +96,8 @@ class WooPosSettingsLocalCatalogViewModel @Inject constructor( loadCatalogStatus() } is PosLocalCatalogSyncResult.Failure -> { - // TBD local catalog: Handle errors backupCatalogData?.let { _state.update { it.copy(catalogStatus = backupCatalogData) } } + onShowSyncErrorDialog?.invoke(result.error) } } } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index e7e0d642b324..3422afe63b7a 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3658,6 +3658,10 @@ Manual Catalog Update Use this refresh only when something seems off - POS keeps data current automatically. Refresh Catalog + Sync error dialog background + Unable to sync catalog + Please check your internet connection and try again. + OK Never Just now From 5dccdef159914b27c894dd3d09f49b2dd4985100 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 17 Oct 2025 08:49:26 +0200 Subject: [PATCH 2/9] Clean up code --- .../woopos/home/items/products/WooPosProductsInDbDataSource.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsInDbDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsInDbDataSource.kt index 5cd0956eed65..b0b183c352df 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsInDbDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsInDbDataSource.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext +import org.wordpress.android.fluxc.model.LocalOrRemoteId import org.wordpress.android.fluxc.store.pos.localcatalog.WooPosLocalCatalogStore import javax.inject.Inject @@ -20,7 +21,7 @@ class WooPosProductsInDbDataSource @Inject constructor( private fun getProductsFromDatabaseFlow(): Flow> { val siteModel = selectedSite.getOrNull() ?: return flow { emit(emptyList()) } - val siteId = org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId(siteModel.id) + val siteId = LocalOrRemoteId.LocalId(siteModel.id) return posLocalCatalogStore.observeProducts(siteId) .map { result -> From a1c366a81d3bf18d894a13f2c0827bb2afefc72e Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 17 Oct 2025 10:18:46 +0200 Subject: [PATCH 3/9] Update dialog layout --- .../WooPosSettingsLocalCatalogScreen.kt | 27 ++++++++++++++++--- WooCommerce/src/main/res/values/strings.xml | 3 ++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt index 1e829f081094..67767af4eb1b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.woopos.settings.details.localcatalog +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -36,9 +37,11 @@ import com.woocommerce.android.ui.woopos.common.composeui.WooPosPreview import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButton import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosButtonState import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosDialogWrapper +import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosOutlinedButton import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosShimmerBox import com.woocommerce.android.ui.woopos.common.composeui.component.WooPosText import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosCornerRadius +import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosIcons import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosSpacing import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTheme import com.woocommerce.android.ui.woopos.common.composeui.designsystem.WooPosTypography @@ -46,14 +49,11 @@ import com.woocommerce.android.ui.woopos.common.composeui.designsystem.toAdaptiv @Composable fun WooPosSettingsLocalCatalogScreen( - onShowSyncErrorDialog: (String) -> Unit, modifier: Modifier = Modifier, viewModel: WooPosSettingsLocalCatalogViewModel = hiltViewModel() ) { val state by viewModel.state.collectAsState() - viewModel.setOnShowSyncErrorDialog(onShowSyncErrorDialog) - WooPosSettingsLocalCatalogScreen( state = state, onToggleCellularData = viewModel::toggleCellularDataUpdate, @@ -287,6 +287,7 @@ private fun SectionTitle(title: String) { @Composable fun WooPosSyncErrorDialog( isVisible: Boolean, + onRetry: () -> Unit, onDismissRequest: () -> Unit, modifier: Modifier = Modifier ) { @@ -328,6 +329,15 @@ fun WooPosSyncErrorDialog( .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { + Image( + imageVector = WooPosIcons.ErrorX, + contentDescription = null, + modifier = Modifier + .padding(WooPosSpacing.Medium.value.toAdaptivePadding()) + ) + + Spacer(modifier = Modifier.height(WooPosSpacing.Large.value.toAdaptivePadding())) + WooPosText( text = stringResource(R.string.woopos_settings_local_catalog_sync_error_dialog_title), style = WooPosTypography.Heading, @@ -346,9 +356,17 @@ fun WooPosSyncErrorDialog( Spacer(modifier = Modifier.height(WooPosSpacing.XLarge.value.toAdaptivePadding())) WooPosButton( + modifier = Modifier.fillMaxWidth(), + onClick = onRetry, + text = stringResource(R.string.woopos_settings_local_catalog_sync_error_dialog_retry_button) + ) + + Spacer(modifier = Modifier.height(WooPosSpacing.Medium.value.toAdaptivePadding())) + + WooPosOutlinedButton( modifier = Modifier.fillMaxWidth(), onClick = onDismissRequest, - text = stringResource(R.string.woopos_settings_local_catalog_sync_error_dialog_button) + text = stringResource(R.string.woopos_settings_local_catalog_sync_error_dialog_cancel_button) ) } } @@ -410,6 +428,7 @@ fun WooPosSyncErrorDialogPreview() { WooPosTheme { WooPosSyncErrorDialog( isVisible = true, + onRetry = {}, onDismissRequest = {} ) } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 3422afe63b7a..f436fcb52e82 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3661,7 +3661,8 @@ Sync error dialog background Unable to sync catalog Please check your internet connection and try again. - OK + Retry + Cancel Never Just now From 10c1e640f86f431b23c59e9b6eadda8674ee8e11 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 17 Oct 2025 10:20:02 +0200 Subject: [PATCH 4/9] Handle Retry and Cancel actions as events --- ...ooPosSettingsChildToParentCommunication.kt | 53 +++++++++++++++++++ .../WooPosSettingsCommunicationModule.kt | 30 +++++++++++ .../woopos/settings/WooPosSettingsScreen.kt | 6 +-- .../settings/WooPosSettingsViewModel.kt | 27 +++++++++- .../details/WooPosSettingsDetailPaneScreen.kt | 5 +- .../WooPosSettingsLocalCatalogViewModel.kt | 26 +++++++-- 6 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsChildToParentCommunication.kt create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsCommunicationModule.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsChildToParentCommunication.kt new file mode 100644 index 000000000000..242085668dac --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsChildToParentCommunication.kt @@ -0,0 +1,53 @@ +package com.woocommerce.android.ui.woopos.settings + +import dagger.hilt.android.scopes.ActivityRetainedScoped +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import javax.inject.Inject + +@ActivityRetainedScoped +class WooPosSettingsChildToParentCommunication @Inject constructor() : + WooPosSettingsChildToParentEventReceiver, WooPosSettingsChildToParentEventSender { + private val _events = MutableSharedFlow() + override val events = _events.asSharedFlow() + + override suspend fun sendToParent(event: SettingsChildToParentEvent) { + _events.emit(event) + } +} + +@ActivityRetainedScoped +class WooPosSettingsParentToChildCommunication @Inject constructor() : + WooPosSettingsParentToChildEventReceiver, WooPosSettingsParentToChildEventSender { + private val _events = MutableSharedFlow() + override val events = _events.asSharedFlow() + + override suspend fun sendToChild(event: SettingsParentToChildEvent) { + _events.emit(event) + } +} + +sealed class SettingsChildToParentEvent { + data class ShowSyncErrorDialog(val errorMessage: String) : SettingsChildToParentEvent() +} + +sealed class SettingsParentToChildEvent { + data object RetrySyncRequested : SettingsParentToChildEvent() +} + +interface WooPosSettingsChildToParentEventReceiver { + val events: Flow +} + +interface WooPosSettingsChildToParentEventSender { + suspend fun sendToParent(event: SettingsChildToParentEvent) +} + +interface WooPosSettingsParentToChildEventReceiver { + val events: Flow +} + +interface WooPosSettingsParentToChildEventSender { + suspend fun sendToChild(event: SettingsParentToChildEvent) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsCommunicationModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsCommunicationModule.kt new file mode 100644 index 000000000000..bcce5bd5cb8d --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsCommunicationModule.kt @@ -0,0 +1,30 @@ +package com.woocommerce.android.ui.woopos.settings + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityRetainedComponent + +@Module +@InstallIn(ActivityRetainedComponent::class) +abstract class WooPosSettingsCommunicationModule { + @Binds + abstract fun bindChildToParentEventSender( + communication: WooPosSettingsChildToParentCommunication + ): WooPosSettingsChildToParentEventSender + + @Binds + abstract fun bindChildToParentEventReceiver( + communication: WooPosSettingsChildToParentCommunication + ): WooPosSettingsChildToParentEventReceiver + + @Binds + abstract fun bindParentToChildEventSender( + communication: WooPosSettingsParentToChildCommunication + ): WooPosSettingsParentToChildEventSender + + @Binds + abstract fun bindParentToChildEventReceiver( + communication: WooPosSettingsParentToChildCommunication + ): WooPosSettingsParentToChildEventReceiver +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt index fc454dbcf067..e1c75fff7d8a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt @@ -50,7 +50,7 @@ fun WooPosSettingsScreen(onNavigationEvent: (WooPosNavigationEvent) -> Unit) { onBack = containerViewModel::navigateBack, onShowProductInfoDialog = containerViewModel::showProductInfoDialog, onShowScanningSetupDialog = containerViewModel::showScanningSetupDialog, - onShowSyncErrorDialog = containerViewModel::showSyncErrorDialog, + onRetrySync = containerViewModel::retrySyncFromDialog, onDismissDialog = containerViewModel::hideDialog ) } @@ -64,7 +64,7 @@ private fun WooPosSettingsContent( onBack: () -> Unit, onShowProductInfoDialog: () -> Unit, onShowScanningSetupDialog: () -> Unit, - onShowSyncErrorDialog: (String) -> Unit, + onRetrySync: () -> Unit, onDismissDialog: () -> Unit ) { Row( @@ -93,7 +93,6 @@ private fun WooPosSettingsContent( onBack = onBack, onShowProductInfoDialog = onShowProductInfoDialog, onShowScanningSetupDialog = onShowScanningSetupDialog, - onShowSyncErrorDialog = onShowSyncErrorDialog, modifier = Modifier .weight(0.7f) .background(MaterialTheme.colorScheme.surfaceContainerLow) @@ -114,6 +113,7 @@ private fun WooPosSettingsContent( WooPosSyncErrorDialog( isVisible = dialogState is WooPosSettingsDialogState.SyncErrorDialog, + onRetry = onRetrySync, onDismissRequest = onDismissDialog ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt index 26578cf9a410..80a0d5355219 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt @@ -21,11 +21,36 @@ import javax.inject.Inject @HiltViewModel class WooPosSettingsViewModel @Inject constructor( - private val analyticsTracker: WooPosAnalyticsTracker + private val analyticsTracker: WooPosAnalyticsTracker, + private val childToParentEventReceiver: WooPosSettingsChildToParentEventReceiver, + private val parentToChildEventSender: WooPosSettingsParentToChildEventSender, ) : ViewModel() { private val _state = MutableStateFlow(WooPosSettingsState()) val state: StateFlow = _state.asStateFlow() + init { + listenToChildEvents() + } + + private fun listenToChildEvents() { + viewModelScope.launch { + childToParentEventReceiver.events.collect { event -> + when (event) { + is SettingsChildToParentEvent.ShowSyncErrorDialog -> { + showSyncErrorDialog(event.errorMessage) + } + } + } + } + } + + fun retrySyncFromDialog() { + hideDialog() + viewModelScope.launch { + parentToChildEventSender.sendToChild(SettingsParentToChildEvent.RetrySyncRequested) + } + } + fun onCategorySelected(category: WooPosSettingsCategory) { trackCategorySelection(category) _state.update { currentState -> diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/WooPosSettingsDetailPaneScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/WooPosSettingsDetailPaneScreen.kt index 28511d46c200..9262c358e9d1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/WooPosSettingsDetailPaneScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/WooPosSettingsDetailPaneScreen.kt @@ -33,7 +33,6 @@ fun WooPosSettingsDetailPaneScreen( onBack: () -> Unit, onShowProductInfoDialog: () -> Unit, onShowScanningSetupDialog: () -> Unit, - onShowSyncErrorDialog: (String) -> Unit, modifier: Modifier = Modifier ) { val currentDestination = state.currentDestination @@ -96,9 +95,7 @@ fun WooPosSettingsDetailPaneScreen( } is WooPosSettingsDetailDestination.LocalCatalog.Overview -> { - WooPosSettingsLocalCatalogScreen( - onShowSyncErrorDialog = onShowSyncErrorDialog - ) + WooPosSettingsLocalCatalogScreen() } is WooPosSettingsDetailDestination.Help.Overview -> { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt index abdcd14f939e..d131e667cd19 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt @@ -6,6 +6,10 @@ import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.woopos.localcatalog.PosLocalCatalogSyncResult import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncRepository import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncScheduler +import com.woocommerce.android.ui.woopos.settings.SettingsChildToParentEvent +import com.woocommerce.android.ui.woopos.settings.SettingsParentToChildEvent +import com.woocommerce.android.ui.woopos.settings.WooPosSettingsChildToParentEventSender +import com.woocommerce.android.ui.woopos.settings.WooPosSettingsParentToChildEventReceiver import com.woocommerce.android.ui.woopos.util.datastore.WooPosPreferencesRepository import com.woocommerce.android.ui.woopos.util.datastore.WooPosSyncTimestampManager import com.woocommerce.android.ui.woopos.util.format.WooPosDateFormatter @@ -25,20 +29,30 @@ class WooPosSettingsLocalCatalogViewModel @Inject constructor( private val dateFormatter: WooPosDateFormatter, private val preferencesRepository: WooPosPreferencesRepository, private val syncScheduler: WooPosLocalCatalogSyncScheduler, + private val childToParentEventSender: WooPosSettingsChildToParentEventSender, + private val parentToChildEventReceiver: WooPosSettingsParentToChildEventReceiver, ) : ViewModel() { private val _state = MutableStateFlow(WooPosSettingsLocalCatalogState()) val state: StateFlow = _state.asStateFlow() - private var onShowSyncErrorDialog: ((String) -> Unit)? = null - init { loadCatalogStatus() listenToCellularDataUpdateValue() + + listenToParentEvents() } - fun setOnShowSyncErrorDialog(callback: (String) -> Unit) { - onShowSyncErrorDialog = callback + private fun listenToParentEvents() { + viewModelScope.launch { + parentToChildEventReceiver.events.collect { event -> + when (event) { + is SettingsParentToChildEvent.RetrySyncRequested -> { + runFullCatalogSync() + } + } + } + } } private fun loadCatalogStatus() { @@ -97,7 +111,9 @@ class WooPosSettingsLocalCatalogViewModel @Inject constructor( } is PosLocalCatalogSyncResult.Failure -> { backupCatalogData?.let { _state.update { it.copy(catalogStatus = backupCatalogData) } } - onShowSyncErrorDialog?.invoke(result.error) + childToParentEventSender.sendToParent( + SettingsChildToParentEvent.ShowSyncErrorDialog(result.error) + ) } } } From 8afe21ebb57dc6ed00f104b477896c046686873c Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 17 Oct 2025 10:56:51 +0200 Subject: [PATCH 5/9] Rename function --- .../android/ui/woopos/settings/WooPosSettingsScreen.kt | 2 +- .../android/ui/woopos/settings/WooPosSettingsViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt index e1c75fff7d8a..66cc10f97ff1 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsScreen.kt @@ -50,7 +50,7 @@ fun WooPosSettingsScreen(onNavigationEvent: (WooPosNavigationEvent) -> Unit) { onBack = containerViewModel::navigateBack, onShowProductInfoDialog = containerViewModel::showProductInfoDialog, onShowScanningSetupDialog = containerViewModel::showScanningSetupDialog, - onRetrySync = containerViewModel::retrySyncFromDialog, + onRetrySync = containerViewModel::onRetrySyncFromDialogClicked, onDismissDialog = containerViewModel::hideDialog ) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt index 80a0d5355219..67a166336526 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt @@ -44,7 +44,7 @@ class WooPosSettingsViewModel @Inject constructor( } } - fun retrySyncFromDialog() { + fun onRetrySyncFromDialogClicked() { hideDialog() viewModelScope.launch { parentToChildEventSender.sendToChild(SettingsParentToChildEvent.RetrySyncRequested) From d2cf99791c3204f45b0d9f1951a38cdc264933a8 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 17 Oct 2025 10:57:26 +0200 Subject: [PATCH 6/9] Add tests for WooPosSettingsViewModel --- .../settings/WooPosSettingsViewModelTest.kt | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModelTest.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModelTest.kt new file mode 100644 index 000000000000..9082d2409758 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModelTest.kt @@ -0,0 +1,146 @@ +package com.woocommerce.android.ui.woopos.settings + +import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +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 +import org.junit.Test +import org.mockito.kotlin.argThat +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@ExperimentalCoroutinesApi +class WooPosSettingsViewModelTest { + + @Rule + @JvmField + val coroutineTestRule = WooPosCoroutineTestRule() + + private val analyticsTracker: WooPosAnalyticsTracker = mock() + private val childToParentEventReceiver: WooPosSettingsChildToParentEventReceiver = mock() + private val parentToChildEventSender: WooPosSettingsParentToChildEventSender = mock() + + @Test + fun `when ShowSyncErrorDialog event collected, then dialog state is shown`() = runTest { + // GIVEN + val errorMessage = "Network error" + whenever(childToParentEventReceiver.events).thenReturn( + flowOf(SettingsChildToParentEvent.ShowSyncErrorDialog(errorMessage)) + ) + + // WHEN + val viewModel = createViewModel() + advanceUntilIdle() + + // THEN + assertThat(viewModel.state.value.dialogState) + .isInstanceOf(WooPosSettingsDialogState.SyncErrorDialog::class.java) + assertThat((viewModel.state.value.dialogState as WooPosSettingsDialogState.SyncErrorDialog).errorMessage) + .isEqualTo(errorMessage) + } + + @Test + fun `given sync error dialog shown, when hideDialog called, then dialog is hidden`() = runTest { + // GIVEN + val errorMessage = "Network error" + whenever(childToParentEventReceiver.events).thenReturn( + flowOf(SettingsChildToParentEvent.ShowSyncErrorDialog(errorMessage)) + ) + val viewModel = createViewModel() + advanceUntilIdle() + + // WHEN + viewModel.hideDialog() + + // THEN + assertThat(viewModel.state.value.dialogState).isEqualTo(WooPosSettingsDialogState.Hidden) + } + + @Test + fun `given sync error dialog shown, when retrySyncFromDialog called, then dialog is hidden`() = runTest { + // GIVEN + val errorMessage = "Network error" + whenever(childToParentEventReceiver.events).thenReturn( + flowOf(SettingsChildToParentEvent.ShowSyncErrorDialog(errorMessage)) + ) + val viewModel = createViewModel() + advanceUntilIdle() + + // WHEN + viewModel.onRetrySyncFromDialogClicked() + advanceUntilIdle() + + // THEN + assertThat(viewModel.state.value.dialogState).isEqualTo(WooPosSettingsDialogState.Hidden) + } + + @Test + fun `given sync error dialog shown, when retrySyncFromDialog called, then RetrySyncRequested event is sent`() = runTest { + // GIVEN + val errorMessage = "Network error" + whenever(childToParentEventReceiver.events).thenReturn( + flowOf(SettingsChildToParentEvent.ShowSyncErrorDialog(errorMessage)) + ) + val viewModel = createViewModel() + advanceUntilIdle() + + // WHEN + viewModel.onRetrySyncFromDialogClicked() + advanceUntilIdle() + + // THEN + verify(parentToChildEventSender).sendToChild( + argThat { + this is SettingsParentToChildEvent.RetrySyncRequested + } + ) + } + + @Test + fun `when default state is created, then dialog state is Hidden`() { + // GIVEN & WHEN + val initialState = WooPosSettingsState() + + // THEN + assertThat(initialState.dialogState).isEqualTo(WooPosSettingsDialogState.Hidden) + } + + @Test + fun `given multiple error events, when received, then dialog state is updated for each`() = runTest { + // GIVEN + val eventsFlow = MutableSharedFlow() + whenever(childToParentEventReceiver.events).thenReturn(eventsFlow) + + val viewModel = createViewModel() + advanceUntilIdle() + + // WHEN & THEN + eventsFlow.emit(SettingsChildToParentEvent.ShowSyncErrorDialog("Error 1")) + advanceUntilIdle() + assertThat(viewModel.state.value.dialogState) + .isInstanceOf(WooPosSettingsDialogState.SyncErrorDialog::class.java) + assertThat((viewModel.state.value.dialogState as WooPosSettingsDialogState.SyncErrorDialog).errorMessage) + .isEqualTo("Error 1") + + viewModel.hideDialog() + + eventsFlow.emit(SettingsChildToParentEvent.ShowSyncErrorDialog("Error 2")) + advanceUntilIdle() + assertThat((viewModel.state.value.dialogState as WooPosSettingsDialogState.SyncErrorDialog).errorMessage) + .isEqualTo("Error 2") + } + + private fun createViewModel(): WooPosSettingsViewModel { + return WooPosSettingsViewModel( + analyticsTracker = analyticsTracker, + childToParentEventReceiver = childToParentEventReceiver, + parentToChildEventSender = parentToChildEventSender, + ) + } +} From 29606b2f87f87aac24bf4b989830f0d286758c72 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Fri, 17 Oct 2025 10:58:40 +0200 Subject: [PATCH 7/9] Add tests for WooPosSettingsLocalCatalogViewModel --- ...WooPosSettingsLocalCatalogViewModelTest.kt | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModelTest.kt diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModelTest.kt new file mode 100644 index 000000000000..ee2f10ff7c24 --- /dev/null +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModelTest.kt @@ -0,0 +1,191 @@ +package com.woocommerce.android.ui.woopos.settings.details.localcatalog + +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.woopos.localcatalog.PosLocalCatalogSyncResult +import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncRepository +import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncScheduler +import com.woocommerce.android.ui.woopos.settings.SettingsChildToParentEvent +import com.woocommerce.android.ui.woopos.settings.SettingsParentToChildEvent +import com.woocommerce.android.ui.woopos.settings.WooPosSettingsChildToParentEventSender +import com.woocommerce.android.ui.woopos.settings.WooPosSettingsParentToChildEventReceiver +import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import com.woocommerce.android.ui.woopos.util.datastore.WooPosPreferencesRepository +import com.woocommerce.android.ui.woopos.util.datastore.WooPosSyncTimestampManager +import com.woocommerce.android.ui.woopos.util.format.WooPosDateFormatter +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +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.kotlin.any +import org.mockito.kotlin.argThat +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.SiteModel + +@ExperimentalCoroutinesApi +class WooPosSettingsLocalCatalogViewModelTest { + + @Rule + @JvmField + val coroutineTestRule = WooPosCoroutineTestRule() + + private val syncTimestampManager: WooPosSyncTimestampManager = mock() + private val localCatalogSyncRepository: WooPosLocalCatalogSyncRepository = mock() + private val selectedSite: SelectedSite = mock() + private val dateFormatter: WooPosDateFormatter = mock() + private val preferencesRepository: WooPosPreferencesRepository = mock() + private val syncScheduler: WooPosLocalCatalogSyncScheduler = mock() + private val childToParentEventSender: WooPosSettingsChildToParentEventSender = mock() + private val parentToChildEventReceiver: WooPosSettingsParentToChildEventReceiver = mock() + + private val siteModel = SiteModel() + + @Before + fun setup() = runTest { + whenever(selectedSite.get()).thenReturn(siteModel) + whenever(dateFormatter.formatCatalogLastUpdate(any(), any())).thenReturn("2 hours ago") + whenever(preferencesRepository.allowCellularDataUpdate).thenReturn(flowOf(false)) + whenever(syncTimestampManager.getProductsLastSyncTimestamp()).thenReturn(0L) + whenever(syncTimestampManager.getVariationsLastSyncTimestamp()).thenReturn(0L) + } + + @Test + fun `given sync fails, when runFullCatalogSync called, then ShowSyncErrorDialog event is sent`() = runTest { + // GIVEN + val errorMessage = "Network error" + whenever(parentToChildEventReceiver.events).thenReturn(MutableSharedFlow()) + whenever(localCatalogSyncRepository.syncLocalCatalogFull(siteModel)) + .thenReturn(PosLocalCatalogSyncResult.Failure.UnexpectedError(errorMessage)) + + val viewModel = createViewModel() + advanceUntilIdle() + + // WHEN + viewModel.runFullCatalogSync() + advanceUntilIdle() + + // THEN + verify(childToParentEventSender).sendToParent( + argThat { + this is SettingsChildToParentEvent.ShowSyncErrorDialog && + this.errorMessage == errorMessage + } + ) + } + + @Test + fun `given sync fails, when runFullCatalogSync called, then catalog status is restored to previous state`() = runTest { + // GIVEN + val errorMessage = "Network error" + whenever(parentToChildEventReceiver.events).thenReturn(MutableSharedFlow()) + whenever(localCatalogSyncRepository.syncLocalCatalogFull(siteModel)) + .thenReturn(PosLocalCatalogSyncResult.Failure.UnexpectedError(errorMessage)) + + val viewModel = createViewModel() + advanceUntilIdle() + + val initialStatus = viewModel.state.value.catalogStatus + + // WHEN + viewModel.runFullCatalogSync() + advanceUntilIdle() + + // THEN + assertThat(viewModel.state.value.catalogStatus).isEqualTo(initialStatus) + } + + @Test + fun `given sync succeeds, when runFullCatalogSync called, then catalog status is reloaded`() = runTest { + // GIVEN + whenever(parentToChildEventReceiver.events).thenReturn(MutableSharedFlow()) + whenever(localCatalogSyncRepository.syncLocalCatalogFull(siteModel)) + .thenReturn( + PosLocalCatalogSyncResult.Success( + productsSynced = 10, + variationsSynced = 5, + syncDurationMs = 1000 + ) + ) + + val viewModel = createViewModel() + advanceUntilIdle() + + // WHEN + viewModel.runFullCatalogSync() + advanceUntilIdle() + + // THEN + assertThat(viewModel.state.value.catalogStatus) + .isInstanceOf(WooPosSettingsLocalCatalogState.CatalogStatus.Available::class.java) + } + + @Test + fun `given RetrySyncRequested event, when received from parent, then runFullCatalogSync is triggered`() = runTest { + // GIVEN + val eventsFlow = MutableSharedFlow() + whenever(parentToChildEventReceiver.events).thenReturn(eventsFlow) + whenever(localCatalogSyncRepository.syncLocalCatalogFull(siteModel)) + .thenReturn( + PosLocalCatalogSyncResult.Success( + productsSynced = 10, + variationsSynced = 5, + syncDurationMs = 1000 + ) + ) + + val viewModel = createViewModel() + advanceUntilIdle() + + // WHEN + eventsFlow.emit(SettingsParentToChildEvent.RetrySyncRequested) + advanceUntilIdle() + + // THEN + assertThat(viewModel.state.value.catalogStatus) + .isInstanceOf(WooPosSettingsLocalCatalogState.CatalogStatus.Available::class.java) + } + + @Test + fun `when runFullCatalogSync called, then catalog status eventually updates to Available`() = runTest { + // GIVEN + whenever(parentToChildEventReceiver.events).thenReturn(MutableSharedFlow()) + whenever(localCatalogSyncRepository.syncLocalCatalogFull(siteModel)) + .thenReturn( + PosLocalCatalogSyncResult.Success( + productsSynced = 10, + variationsSynced = 5, + syncDurationMs = 1000 + ) + ) + + val viewModel = createViewModel() + advanceUntilIdle() + + // WHEN + viewModel.runFullCatalogSync() + advanceUntilIdle() + + // THEN + assertThat(viewModel.state.value.catalogStatus) + .isInstanceOf(WooPosSettingsLocalCatalogState.CatalogStatus.Available::class.java) + } + + private fun createViewModel(): WooPosSettingsLocalCatalogViewModel { + return WooPosSettingsLocalCatalogViewModel( + syncTimestampManager = syncTimestampManager, + localCatalogSyncRepository = localCatalogSyncRepository, + selectedSite = selectedSite, + dateFormatter = dateFormatter, + preferencesRepository = preferencesRepository, + syncScheduler = syncScheduler, + childToParentEventSender = childToParentEventSender, + parentToChildEventReceiver = parentToChildEventReceiver, + ) + } +} From 0c1b2e085cb0deb00690f5be3f8d2bc9eedc67c6 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 20 Oct 2025 15:43:42 +0200 Subject: [PATCH 8/9] Reuse WooPosHomeCommunication in settings --- .../WooPosHomeChildToParentCommunication.kt | 4 ++ .../WooPosHomeParentToChildCommunication.kt | 4 ++ .../ui/woopos/home/WooPosHomeViewModel.kt | 2 + .../woopos/home/cart/WooPosCartViewModel.kt | 3 +- .../home/items/WooPosItemsSearchHelper.kt | 1 + .../woopos/home/items/WooPosItemsViewModel.kt | 3 +- .../items/products/WooPosProductsViewModel.kt | 3 +- .../search/WooPosItemsSearchViewModel.kt | 1 + .../home/totals/WooPosTotalsViewModel.kt | 3 +- ...ooPosSettingsChildToParentCommunication.kt | 53 ------------------- .../WooPosSettingsCommunicationModule.kt | 30 ----------- .../settings/WooPosSettingsViewModel.kt | 13 +++-- .../WooPosSettingsLocalCatalogViewModel.kt | 17 +++--- .../settings/WooPosSettingsViewModelTest.kt | 26 +++++---- ...WooPosSettingsLocalCatalogViewModelTest.kt | 18 +++---- 15 files changed, 62 insertions(+), 119 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsChildToParentCommunication.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsCommunicationModule.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt index 6473fc323a50..365f4f9d27e0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeChildToParentCommunication.kt @@ -99,6 +99,10 @@ sealed class ChildToParentEvent { val discountAmount: BigDecimal, ) } + + sealed class SettingsEvent : ChildToParentEvent() { + data class ShowSyncErrorDialog(val errorMessage: String) : SettingsEvent() + } } interface WooPosChildrenToParentEventReceiver { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt index e2b82255d23a..f62af29de663 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeParentToChildCommunication.kt @@ -90,6 +90,10 @@ sealed class ParentToChildrenEvent { val discountAmount: BigDecimal, ) } + + sealed class SettingsEvent : ParentToChildrenEvent() { + data object RetrySyncRequested : SettingsEvent() + } } interface WooPosParentToChildrenEventReceiver { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt index eca388187520..87c51df48319 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModel.kt @@ -240,6 +240,8 @@ class WooPosHomeViewModel @Inject constructor( ChildToParentEvent.RefreshProductList -> { sendEventToChildren(ParentToChildrenEvent.RefreshProductList) } + + is ChildToParentEvent.SettingsEvent -> Unit } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt index ca95ff8b448d..0c0871b987f5 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/cart/WooPosCartViewModel.kt @@ -216,7 +216,8 @@ class WooPosCartViewModel @Inject constructor( ParentToChildrenEvent.SearchEvent.Finished, ParentToChildrenEvent.SearchEvent.Started, ParentToChildrenEvent.RefreshProductList, - is ParentToChildrenEvent.CouponsRemoved -> Unit + is ParentToChildrenEvent.CouponsRemoved, + is ParentToChildrenEvent.SettingsEvent -> Unit is ParentToChildrenEvent.CouponsValidationFailed -> { onCouponsValidationFails() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelper.kt index 33f421483774..c7552996cfe6 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/WooPosItemsSearchHelper.kt @@ -68,6 +68,7 @@ class WooPosItemsSearchHelper @Inject constructor( is ParentToChildrenEvent.RemoveCouponsClicked -> Unit is ParentToChildrenEvent.CouponsValidationFailed -> Unit is ParentToChildrenEvent.OrderSuccessfullyPaid -> Unit + is ParentToChildrenEvent.SettingsEvent -> Unit } } } 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 33b11d875f28..b4dd213c7456 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 @@ -95,7 +95,8 @@ class WooPosItemsViewModel @Inject constructor( ParentToChildrenEvent.SearchEvent.Finished, is ParentToChildrenEvent.SearchEvent.RecentSearchSelected, ParentToChildrenEvent.SearchEvent.Started, - is ParentToChildrenEvent.BarcodeEvent -> Unit + is ParentToChildrenEvent.BarcodeEvent, + is ParentToChildrenEvent.SettingsEvent -> Unit is ParentToChildrenEvent.OrderSuccessfullyPaid -> _viewState.value = initialState() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModel.kt index 7669de1c284a..7295d5e51f0c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/products/WooPosProductsViewModel.kt @@ -91,7 +91,8 @@ class WooPosProductsViewModel @Inject constructor( is ParentToChildrenEvent.SearchEvent.ChangedQuery, ParentToChildrenEvent.SearchEvent.Finished, is ParentToChildrenEvent.SearchEvent.RecentSearchSelected, - ParentToChildrenEvent.SearchEvent.Started -> Unit + ParentToChildrenEvent.SearchEvent.Started, + is ParentToChildrenEvent.SettingsEvent -> Unit } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModel.kt index 1e01cd4123b3..c0b3b69b8d81 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/items/search/WooPosItemsSearchViewModel.kt @@ -193,6 +193,7 @@ class WooPosItemsSearchViewModel @Inject constructor( is ParentToChildrenEvent.CouponsRemoved -> Unit is ParentToChildrenEvent.RefreshProductList -> Unit is ParentToChildrenEvent.CouponsValidationFailed -> Unit + is ParentToChildrenEvent.SettingsEvent -> Unit is ParentToChildrenEvent.ItemClickedInItemsList -> { if (event.itemData is ItemClickedData.Product.Variation && searchHelper.isSearchOpen()) { storeRecentSearch() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt index dd9b3c593940..2456b0400ff8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/totals/WooPosTotalsViewModel.kt @@ -325,7 +325,8 @@ class WooPosTotalsViewModel @Inject constructor( ParentToChildrenEvent.RemoveCouponsClicked, ParentToChildrenEvent.RefreshProductList, ParentToChildrenEvent.CouponsValidationFailed, - is ParentToChildrenEvent.BarcodeEvent -> Unit + is ParentToChildrenEvent.BarcodeEvent, + is ParentToChildrenEvent.SettingsEvent -> Unit } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsChildToParentCommunication.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsChildToParentCommunication.kt deleted file mode 100644 index 242085668dac..000000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsChildToParentCommunication.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.woocommerce.android.ui.woopos.settings - -import dagger.hilt.android.scopes.ActivityRetainedScoped -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import javax.inject.Inject - -@ActivityRetainedScoped -class WooPosSettingsChildToParentCommunication @Inject constructor() : - WooPosSettingsChildToParentEventReceiver, WooPosSettingsChildToParentEventSender { - private val _events = MutableSharedFlow() - override val events = _events.asSharedFlow() - - override suspend fun sendToParent(event: SettingsChildToParentEvent) { - _events.emit(event) - } -} - -@ActivityRetainedScoped -class WooPosSettingsParentToChildCommunication @Inject constructor() : - WooPosSettingsParentToChildEventReceiver, WooPosSettingsParentToChildEventSender { - private val _events = MutableSharedFlow() - override val events = _events.asSharedFlow() - - override suspend fun sendToChild(event: SettingsParentToChildEvent) { - _events.emit(event) - } -} - -sealed class SettingsChildToParentEvent { - data class ShowSyncErrorDialog(val errorMessage: String) : SettingsChildToParentEvent() -} - -sealed class SettingsParentToChildEvent { - data object RetrySyncRequested : SettingsParentToChildEvent() -} - -interface WooPosSettingsChildToParentEventReceiver { - val events: Flow -} - -interface WooPosSettingsChildToParentEventSender { - suspend fun sendToParent(event: SettingsChildToParentEvent) -} - -interface WooPosSettingsParentToChildEventReceiver { - val events: Flow -} - -interface WooPosSettingsParentToChildEventSender { - suspend fun sendToChild(event: SettingsParentToChildEvent) -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsCommunicationModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsCommunicationModule.kt deleted file mode 100644 index bcce5bd5cb8d..000000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsCommunicationModule.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.woocommerce.android.ui.woopos.settings - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityRetainedComponent - -@Module -@InstallIn(ActivityRetainedComponent::class) -abstract class WooPosSettingsCommunicationModule { - @Binds - abstract fun bindChildToParentEventSender( - communication: WooPosSettingsChildToParentCommunication - ): WooPosSettingsChildToParentEventSender - - @Binds - abstract fun bindChildToParentEventReceiver( - communication: WooPosSettingsChildToParentCommunication - ): WooPosSettingsChildToParentEventReceiver - - @Binds - abstract fun bindParentToChildEventSender( - communication: WooPosSettingsParentToChildCommunication - ): WooPosSettingsParentToChildEventSender - - @Binds - abstract fun bindParentToChildEventReceiver( - communication: WooPosSettingsParentToChildCommunication - ): WooPosSettingsParentToChildEventReceiver -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt index 67a166336526..957ca5e5e08c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModel.kt @@ -2,6 +2,10 @@ package com.woocommerce.android.ui.woopos.settings import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.woocommerce.android.ui.woopos.home.ChildToParentEvent +import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent +import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventReceiver +import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventSender import com.woocommerce.android.ui.woopos.settings.categories.WooPosSettingsCategory import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.HardwareTapped @@ -22,8 +26,8 @@ import javax.inject.Inject @HiltViewModel class WooPosSettingsViewModel @Inject constructor( private val analyticsTracker: WooPosAnalyticsTracker, - private val childToParentEventReceiver: WooPosSettingsChildToParentEventReceiver, - private val parentToChildEventSender: WooPosSettingsParentToChildEventSender, + private val childToParentEventReceiver: WooPosChildrenToParentEventReceiver, + private val parentToChildEventSender: WooPosParentToChildrenEventSender, ) : ViewModel() { private val _state = MutableStateFlow(WooPosSettingsState()) val state: StateFlow = _state.asStateFlow() @@ -36,9 +40,10 @@ class WooPosSettingsViewModel @Inject constructor( viewModelScope.launch { childToParentEventReceiver.events.collect { event -> when (event) { - is SettingsChildToParentEvent.ShowSyncErrorDialog -> { + is ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog -> { showSyncErrorDialog(event.errorMessage) } + else -> Unit } } } @@ -47,7 +52,7 @@ class WooPosSettingsViewModel @Inject constructor( fun onRetrySyncFromDialogClicked() { hideDialog() viewModelScope.launch { - parentToChildEventSender.sendToChild(SettingsParentToChildEvent.RetrySyncRequested) + parentToChildEventSender.sendToChildren(ParentToChildrenEvent.SettingsEvent.RetrySyncRequested) } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt index d131e667cd19..f528e1db23fa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModel.kt @@ -3,13 +3,13 @@ package com.woocommerce.android.ui.woopos.settings.details.localcatalog import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.woopos.home.ChildToParentEvent +import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent +import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender +import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver import com.woocommerce.android.ui.woopos.localcatalog.PosLocalCatalogSyncResult import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncRepository import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncScheduler -import com.woocommerce.android.ui.woopos.settings.SettingsChildToParentEvent -import com.woocommerce.android.ui.woopos.settings.SettingsParentToChildEvent -import com.woocommerce.android.ui.woopos.settings.WooPosSettingsChildToParentEventSender -import com.woocommerce.android.ui.woopos.settings.WooPosSettingsParentToChildEventReceiver import com.woocommerce.android.ui.woopos.util.datastore.WooPosPreferencesRepository import com.woocommerce.android.ui.woopos.util.datastore.WooPosSyncTimestampManager import com.woocommerce.android.ui.woopos.util.format.WooPosDateFormatter @@ -29,8 +29,8 @@ class WooPosSettingsLocalCatalogViewModel @Inject constructor( private val dateFormatter: WooPosDateFormatter, private val preferencesRepository: WooPosPreferencesRepository, private val syncScheduler: WooPosLocalCatalogSyncScheduler, - private val childToParentEventSender: WooPosSettingsChildToParentEventSender, - private val parentToChildEventReceiver: WooPosSettingsParentToChildEventReceiver, + private val childToParentEventSender: WooPosChildrenToParentEventSender, + private val parentToChildEventReceiver: WooPosParentToChildrenEventReceiver, ) : ViewModel() { private val _state = MutableStateFlow(WooPosSettingsLocalCatalogState()) val state: StateFlow = _state.asStateFlow() @@ -47,9 +47,10 @@ class WooPosSettingsLocalCatalogViewModel @Inject constructor( viewModelScope.launch { parentToChildEventReceiver.events.collect { event -> when (event) { - is SettingsParentToChildEvent.RetrySyncRequested -> { + is ParentToChildrenEvent.SettingsEvent.RetrySyncRequested -> { runFullCatalogSync() } + else -> Unit } } } @@ -112,7 +113,7 @@ class WooPosSettingsLocalCatalogViewModel @Inject constructor( is PosLocalCatalogSyncResult.Failure -> { backupCatalogData?.let { _state.update { it.copy(catalogStatus = backupCatalogData) } } childToParentEventSender.sendToParent( - SettingsChildToParentEvent.ShowSyncErrorDialog(result.error) + ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog(result.error) ) } } diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModelTest.kt index 9082d2409758..a387f1f8233d 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/WooPosSettingsViewModelTest.kt @@ -1,5 +1,9 @@ package com.woocommerce.android.ui.woopos.settings +import com.woocommerce.android.ui.woopos.home.ChildToParentEvent +import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent +import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventReceiver +import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventSender import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -23,15 +27,15 @@ class WooPosSettingsViewModelTest { val coroutineTestRule = WooPosCoroutineTestRule() private val analyticsTracker: WooPosAnalyticsTracker = mock() - private val childToParentEventReceiver: WooPosSettingsChildToParentEventReceiver = mock() - private val parentToChildEventSender: WooPosSettingsParentToChildEventSender = mock() + private val childToParentEventReceiver: WooPosChildrenToParentEventReceiver = mock() + private val parentToChildEventSender: WooPosParentToChildrenEventSender = mock() @Test fun `when ShowSyncErrorDialog event collected, then dialog state is shown`() = runTest { // GIVEN val errorMessage = "Network error" whenever(childToParentEventReceiver.events).thenReturn( - flowOf(SettingsChildToParentEvent.ShowSyncErrorDialog(errorMessage)) + flowOf(ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog(errorMessage)) ) // WHEN @@ -50,7 +54,7 @@ class WooPosSettingsViewModelTest { // GIVEN val errorMessage = "Network error" whenever(childToParentEventReceiver.events).thenReturn( - flowOf(SettingsChildToParentEvent.ShowSyncErrorDialog(errorMessage)) + flowOf(ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog(errorMessage)) ) val viewModel = createViewModel() advanceUntilIdle() @@ -67,7 +71,7 @@ class WooPosSettingsViewModelTest { // GIVEN val errorMessage = "Network error" whenever(childToParentEventReceiver.events).thenReturn( - flowOf(SettingsChildToParentEvent.ShowSyncErrorDialog(errorMessage)) + flowOf(ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog(errorMessage)) ) val viewModel = createViewModel() advanceUntilIdle() @@ -85,7 +89,7 @@ class WooPosSettingsViewModelTest { // GIVEN val errorMessage = "Network error" whenever(childToParentEventReceiver.events).thenReturn( - flowOf(SettingsChildToParentEvent.ShowSyncErrorDialog(errorMessage)) + flowOf(ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog(errorMessage)) ) val viewModel = createViewModel() advanceUntilIdle() @@ -95,9 +99,9 @@ class WooPosSettingsViewModelTest { advanceUntilIdle() // THEN - verify(parentToChildEventSender).sendToChild( + verify(parentToChildEventSender).sendToChildren( argThat { - this is SettingsParentToChildEvent.RetrySyncRequested + this is ParentToChildrenEvent.SettingsEvent.RetrySyncRequested } ) } @@ -114,14 +118,14 @@ class WooPosSettingsViewModelTest { @Test fun `given multiple error events, when received, then dialog state is updated for each`() = runTest { // GIVEN - val eventsFlow = MutableSharedFlow() + val eventsFlow = MutableSharedFlow() whenever(childToParentEventReceiver.events).thenReturn(eventsFlow) val viewModel = createViewModel() advanceUntilIdle() // WHEN & THEN - eventsFlow.emit(SettingsChildToParentEvent.ShowSyncErrorDialog("Error 1")) + eventsFlow.emit(ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog("Error 1")) advanceUntilIdle() assertThat(viewModel.state.value.dialogState) .isInstanceOf(WooPosSettingsDialogState.SyncErrorDialog::class.java) @@ -130,7 +134,7 @@ class WooPosSettingsViewModelTest { viewModel.hideDialog() - eventsFlow.emit(SettingsChildToParentEvent.ShowSyncErrorDialog("Error 2")) + eventsFlow.emit(ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog("Error 2")) advanceUntilIdle() assertThat((viewModel.state.value.dialogState as WooPosSettingsDialogState.SyncErrorDialog).errorMessage) .isEqualTo("Error 2") diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModelTest.kt index ee2f10ff7c24..e4f422a93ddd 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogViewModelTest.kt @@ -1,13 +1,13 @@ package com.woocommerce.android.ui.woopos.settings.details.localcatalog import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.ui.woopos.home.ChildToParentEvent +import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent +import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender +import com.woocommerce.android.ui.woopos.home.WooPosParentToChildrenEventReceiver import com.woocommerce.android.ui.woopos.localcatalog.PosLocalCatalogSyncResult import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncRepository import com.woocommerce.android.ui.woopos.localcatalog.WooPosLocalCatalogSyncScheduler -import com.woocommerce.android.ui.woopos.settings.SettingsChildToParentEvent -import com.woocommerce.android.ui.woopos.settings.SettingsParentToChildEvent -import com.woocommerce.android.ui.woopos.settings.WooPosSettingsChildToParentEventSender -import com.woocommerce.android.ui.woopos.settings.WooPosSettingsParentToChildEventReceiver import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import com.woocommerce.android.ui.woopos.util.datastore.WooPosPreferencesRepository import com.woocommerce.android.ui.woopos.util.datastore.WooPosSyncTimestampManager @@ -41,8 +41,8 @@ class WooPosSettingsLocalCatalogViewModelTest { private val dateFormatter: WooPosDateFormatter = mock() private val preferencesRepository: WooPosPreferencesRepository = mock() private val syncScheduler: WooPosLocalCatalogSyncScheduler = mock() - private val childToParentEventSender: WooPosSettingsChildToParentEventSender = mock() - private val parentToChildEventReceiver: WooPosSettingsParentToChildEventReceiver = mock() + private val childToParentEventSender: WooPosChildrenToParentEventSender = mock() + private val parentToChildEventReceiver: WooPosParentToChildrenEventReceiver = mock() private val siteModel = SiteModel() @@ -73,7 +73,7 @@ class WooPosSettingsLocalCatalogViewModelTest { // THEN verify(childToParentEventSender).sendToParent( argThat { - this is SettingsChildToParentEvent.ShowSyncErrorDialog && + this is ChildToParentEvent.SettingsEvent.ShowSyncErrorDialog && this.errorMessage == errorMessage } ) @@ -128,7 +128,7 @@ class WooPosSettingsLocalCatalogViewModelTest { @Test fun `given RetrySyncRequested event, when received from parent, then runFullCatalogSync is triggered`() = runTest { // GIVEN - val eventsFlow = MutableSharedFlow() + val eventsFlow = MutableSharedFlow() whenever(parentToChildEventReceiver.events).thenReturn(eventsFlow) whenever(localCatalogSyncRepository.syncLocalCatalogFull(siteModel)) .thenReturn( @@ -143,7 +143,7 @@ class WooPosSettingsLocalCatalogViewModelTest { advanceUntilIdle() // WHEN - eventsFlow.emit(SettingsParentToChildEvent.RetrySyncRequested) + eventsFlow.emit(ParentToChildrenEvent.SettingsEvent.RetrySyncRequested) advanceUntilIdle() // THEN From 0a56aa7549384a17d456f0f7af10a1df904dec55 Mon Sep 17 00:00:00 2001 From: samiuelson Date: Mon, 20 Oct 2025 15:56:18 +0200 Subject: [PATCH 9/9] Move modifier to 1st arg position --- .../localcatalog/WooPosSettingsLocalCatalogScreen.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt index 67767af4eb1b..f93d019dfd4e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/settings/details/localcatalog/WooPosSettingsLocalCatalogScreen.kt @@ -64,10 +64,10 @@ fun WooPosSettingsLocalCatalogScreen( @Composable private fun WooPosSettingsLocalCatalogScreen( + modifier: Modifier = Modifier, state: WooPosSettingsLocalCatalogState, onToggleCellularData: (Boolean) -> Unit, - onRefreshCatalog: () -> Unit, - modifier: Modifier = Modifier + onRefreshCatalog: () -> Unit ) { Column( modifier = modifier @@ -286,10 +286,10 @@ private fun SectionTitle(title: String) { @Composable fun WooPosSyncErrorDialog( + modifier: Modifier = Modifier, isVisible: Boolean, onRetry: () -> Unit, - onDismissRequest: () -> Unit, - modifier: Modifier = Modifier + onDismissRequest: () -> Unit ) { WooPosDialogWrapper( modifier = modifier,