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 d7f3769eacc..105ba84ff87 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 @@ -29,6 +29,7 @@ sealed class ChildToParentEvent { data object FullScreen : ProductsStatusChanged() data object WithCart : ProductsStatusChanged() } + data object NoInternet : ChildToParentEvent() } interface WooPosChildrenToParentEventReceiver { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt index c7e7267a0ab..3e3242edf37 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeScreen.kt @@ -22,6 +22,7 @@ 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.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -41,6 +42,7 @@ import com.woocommerce.android.ui.woopos.home.toolbar.WooPosFloatingToolbar import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsScreen import com.woocommerce.android.ui.woopos.home.totals.WooPosTotalsScreenPreview import com.woocommerce.android.ui.woopos.root.navigation.WooPosNavigationEvent +import org.wordpress.android.util.ToastUtils @Composable fun WooPosHomeScreen( @@ -48,6 +50,16 @@ fun WooPosHomeScreen( ) { val viewModel: WooPosHomeViewModel = hiltViewModel() val state = viewModel.state.collectAsState().value + val context = LocalContext.current + LaunchedEffect(viewModel.toastEvent) { + viewModel.toastEvent.collect { message -> + ToastUtils.showToast( + context, + context.getString(message.message), + ToastUtils.Duration.LONG + ) + } + } WooPosHomeScreen( state, 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 c1c52e8fb2b..2e1f6c56054 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 @@ -1,10 +1,14 @@ package com.woocommerce.android.ui.woopos.home +import androidx.annotation.StringRes import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.woocommerce.android.R import com.woocommerce.android.viewmodel.getStateFlow import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import javax.inject.Inject @@ -21,11 +25,18 @@ class WooPosHomeViewModel @Inject constructor( initialValue = WooPosHomeState( screenPositionState = WooPosHomeState.ScreenPositionState.Cart.Visible, productsInfoDialog = WooPosHomeState.ProductsInfoDialog(isVisible = false), - exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = false) + exitConfirmationDialog = WooPosHomeState.ExitConfirmationDialog(isVisible = false), ) ) val state: StateFlow = _state + private val _toastEvent = MutableSharedFlow() + val toastEvent: SharedFlow = _toastEvent + + data class Toast( + @StringRes val message: Int, + ) + init { listenBottomEvents() } @@ -119,6 +130,12 @@ class WooPosHomeViewModel @Inject constructor( productsInfoDialog = WooPosHomeState.ProductsInfoDialog(isVisible = true) ) } + + ChildToParentEvent.NoInternet -> { + viewModelScope.launch { + _toastEvent.emit(Toast(R.string.woopos_no_internet_message)) + } + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModel.kt index 88495a1ffcb..5f83dd9bc22 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModel.kt @@ -15,6 +15,7 @@ import com.woocommerce.android.ui.woopos.home.toolbar.WooPosToolbarUIEvent.OnCar import com.woocommerce.android.ui.woopos.home.toolbar.WooPosToolbarUIEvent.OnOutsideOfToolbarMenuClicked import com.woocommerce.android.ui.woopos.home.toolbar.WooPosToolbarUIEvent.OnToolbarMenuClicked import com.woocommerce.android.ui.woopos.support.WooPosGetSupportFacade +import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -26,6 +27,7 @@ class WooPosToolbarViewModel @Inject constructor( private val cardReaderFacade: WooPosCardReaderFacade, private val childrenToParentEventSender: WooPosChildrenToParentEventSender, private val getSupportFacade: WooPosGetSupportFacade, + private val networkStatus: WooPosNetworkStatus, ) : ViewModel() { private val _state = MutableStateFlow( WooPosToolbarState( @@ -92,7 +94,15 @@ class WooPosToolbarViewModel @Inject constructor( cardReaderFacade.disconnectFromReader() } } - WooPosToolbarState.WooPosCardReaderStatus.NotConnected -> cardReaderFacade.connectToReader() + WooPosToolbarState.WooPosCardReaderStatus.NotConnected -> { + if (!networkStatus.isConnected()) { + viewModelScope.launch { + childrenToParentEventSender.sendToParent(ChildToParentEvent.NoInternet) + } + } else { + cardReaderFacade.connectToReader() + } + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/util/WooPosNetworkStatus.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/util/WooPosNetworkStatus.kt new file mode 100644 index 00000000000..092838dfa2d --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/util/WooPosNetworkStatus.kt @@ -0,0 +1,13 @@ +package com.woocommerce.android.ui.woopos.util + +import android.content.Context +import dagger.Reusable +import org.wordpress.android.util.NetworkUtils +import javax.inject.Inject + +@Reusable +class WooPosNetworkStatus @Inject constructor( + private val context: Context +) { + fun isConnected() = NetworkUtils.isNetworkAvailable(context) +} diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 258ccfa49dd..968d7983c4b 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4252,6 +4252,7 @@ Toolbar with card reader status. Menu is open. Double tap to interact. Open toolbar menu 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. Customer diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModelTest.kt index 04cfd05688e..dee426e3e59 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarViewModelTest.kt @@ -7,6 +7,7 @@ import com.woocommerce.android.ui.woopos.home.ChildToParentEvent import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.support.WooPosGetSupportFacade import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule +import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest @@ -14,6 +15,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Rule import org.junit.Test import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -27,6 +29,7 @@ class WooPosToolbarViewModelTest { } private val getSupportFacade: WooPosGetSupportFacade = mock() private val childrenToParentEventSender: WooPosChildrenToParentEventSender = mock() + private val networkStatus: WooPosNetworkStatus = mock() @Test fun `given card reader status is NotConnected, when initialized, then state should be NotConnected`() = runTest { @@ -105,6 +108,7 @@ class WooPosToolbarViewModelTest { fun `when ConnectToAReaderClicked passed, then connect to reader should be called`() = runTest { // GIVEN whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + whenever(networkStatus.isConnected()).thenReturn(true) val viewModel = createViewModel() // WHEN @@ -132,30 +136,33 @@ class WooPosToolbarViewModelTest { } @Test - fun `given card reader status is Connected, when OnCardReaderStatusClicked, then disconnect from reader should be called`() = runTest { - // GIVEN - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.Connected(mock()))) - val viewModel = createViewModel() + fun `given card reader status is Connected, when OnCardReaderStatusClicked, then disconnect from reader should be called`() = + runTest { + // GIVEN + whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.Connected(mock()))) + val viewModel = createViewModel() - // WHEN - viewModel.onUiEvent(WooPosToolbarUIEvent.OnCardReaderStatusClicked) + // WHEN + viewModel.onUiEvent(WooPosToolbarUIEvent.OnCardReaderStatusClicked) - // THEN - verify(cardReaderFacade).disconnectFromReader() - } + // THEN + verify(cardReaderFacade).disconnectFromReader() + } @Test - fun `given card reader status is NotConnected, when OnCardReaderStatusClicked, then connect to reader should be called`() = runTest { - // GIVEN - whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) - val viewModel = createViewModel() + fun `given card reader status is NotConnected, when OnCardReaderStatusClicked, then connect to reader should be called`() = + runTest { + // GIVEN + whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + whenever(networkStatus.isConnected()).thenReturn(true) + val viewModel = createViewModel() - // WHEN - viewModel.onUiEvent(WooPosToolbarUIEvent.OnCardReaderStatusClicked) + // WHEN + viewModel.onUiEvent(WooPosToolbarUIEvent.OnCardReaderStatusClicked) - // THEN - verify(cardReaderFacade).connectToReader() - } + // THEN + verify(cardReaderFacade).connectToReader() + } @Test fun `when get support clicked, then should open support form`() { @@ -173,9 +180,38 @@ class WooPosToolbarViewModelTest { verify(getSupportFacade).openSupportForm() } + @Test + fun `given there is no internet, when trying to connect card reader, then trigger proper event`() = runTest { + // GIVEN + whenever(networkStatus.isConnected()).thenReturn(false) + whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + + // WHEN + val viewModel = createViewModel() + viewModel.onUiEvent(WooPosToolbarUIEvent.OnCardReaderStatusClicked) + + // THEN + verify(childrenToParentEventSender).sendToParent(ChildToParentEvent.NoInternet) + } + + @Test + fun `given there is no internet, when trying to connect card reader, then connect card reader method is not called`() = runTest { + // GIVEN + whenever(networkStatus.isConnected()).thenReturn(false) + whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + + // WHEN + val viewModel = createViewModel() + viewModel.onUiEvent(WooPosToolbarUIEvent.OnCardReaderStatusClicked) + + // THEN + verify(cardReaderFacade, never()).connectToReader() + } + private fun createViewModel() = WooPosToolbarViewModel( cardReaderFacade, childrenToParentEventSender, getSupportFacade, + networkStatus ) }