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 73782fcd6d1..cdd25daa5f2 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,8 @@ import com.woocommerce.android.ui.woopos.home.toolbar.WooPosToolbarUIEvent.MenuI import com.woocommerce.android.ui.woopos.home.toolbar.WooPosToolbarUIEvent.OnOutsideOfToolbarMenuClicked import com.woocommerce.android.ui.woopos.home.toolbar.WooPosToolbarUIEvent.OnToolbarMenuClicked import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -33,6 +35,8 @@ class WooPosToolbarViewModel @Inject constructor( ) val state: StateFlow = _state + private var debounceJob: Job? = null + init { viewModelScope.launch { cardReaderFacade.readerStatus.collect { @@ -85,7 +89,9 @@ class WooPosToolbarViewModel @Inject constructor( private fun handleConnectToReaderButtonClicked() { if (_state.value.cardReaderStatus != WooPosToolbarState.WooPosCardReaderStatus.Connected) { - cardReaderFacade.connectToReader() + debounce { + cardReaderFacade.connectToReader() + } } } @@ -96,6 +102,16 @@ class WooPosToolbarViewModel @Inject constructor( } } + private fun debounce(block: () -> Unit) { + if (debounceJob?.isActive == true) { + return + } + debounceJob = viewModelScope.launch { + block() + delay(DEBOUNCE_TIME_MS) + } + } + private companion object { val toolbarMenuItems = listOf( WooPosToolbarState.Menu.MenuItem( @@ -107,5 +123,11 @@ class WooPosToolbarViewModel @Inject constructor( icon = R.drawable.woopos_ic_exit_pos, ), ) + + private const val DEBOUNCE_TIME_MS = 800L + } + + override fun onCleared() { + debounceJob?.cancel() } } 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 3c3fbc7cc2f..2942c4cbad9 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 @@ -8,11 +8,13 @@ import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule import kotlinx.coroutines.ExperimentalCoroutinesApi 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.mock +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -129,6 +131,40 @@ class WooPosToolbarViewModelTest { assertThat(viewModel.state.value.menu).isEqualTo(WooPosToolbarState.Menu.Hidden) } + @Test + fun `when connect to card reader clicked multiple times, then debounce prevents multiple clicks`() = runTest { + // GIVEN + whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + val viewModel = createViewModel() + + // WHEN + viewModel.onUiEvent(WooPosToolbarUIEvent.ConnectToAReaderClicked) + viewModel.onUiEvent(WooPosToolbarUIEvent.ConnectToAReaderClicked) + viewModel.onUiEvent(WooPosToolbarUIEvent.ConnectToAReaderClicked) + advanceUntilIdle() + + // THEN + verify(cardReaderFacade, times(1)).connectToReader() + } + + @Test + fun `when connect to card reader clicked multiple times after delay, then debounce handles all clicks`() = runTest { + // GIVEN + whenever(cardReaderFacade.readerStatus).thenReturn(flowOf(CardReaderStatus.NotConnected())) + val viewModel = createViewModel() + + // WHEN + viewModel.onUiEvent(WooPosToolbarUIEvent.ConnectToAReaderClicked) + advanceUntilIdle() + viewModel.onUiEvent(WooPosToolbarUIEvent.ConnectToAReaderClicked) + advanceUntilIdle() + viewModel.onUiEvent(WooPosToolbarUIEvent.ConnectToAReaderClicked) + advanceUntilIdle() + + // THEN + verify(cardReaderFacade, times(3)).connectToReader() + } + private fun createViewModel() = WooPosToolbarViewModel( cardReaderFacade, childrenToParentEventSender