Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ class MockCardReaderManagerModule {

override fun cancelPayment(paymentData: PaymentData) {}

override fun cancelReconnection() {}

override suspend fun startAsyncSoftwareUpdate() {}

override suspend fun clearCachedCredentials() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,10 @@ class CardReaderConnectViewModel @Inject constructor(
connectionStarted = true
viewState.value = provideConnectingState()
}

CardReaderStatus.Reconnecting -> {
// Reconnecting is handled by the SDK, no action needed during connection flow
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailVie
import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailViewModel.ViewState.ConnectedState
import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailViewModel.ViewState.Loading
import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailViewModel.ViewState.NotConnectedState
import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailViewModel.ViewState.ReconnectingState
import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderType.EXTERNAL
import com.woocommerce.android.ui.payments.cardreader.update.CardReaderUpdateDialogFragment
import com.woocommerce.android.ui.payments.cardreader.update.CardReaderUpdateViewModel.UpdateResult
Expand Down Expand Up @@ -180,6 +181,25 @@ class CardReaderDetailFragment : BaseFragment(R.layout.fragment_card_reader_deta

Loading -> {
}

is ReconnectingState -> {
with(binding.readerDisconnectedState) {
UiHelpers.setTextOrHide(cardReaderDetailConnectHeaderLabel, state.headerLabel)
UiHelpers.setImageOrHideInLandscapeOnCompactScreenHeightSizeClass(
cardReaderDetailIllustration,
state.illustration
)
cardReaderDetailFirstHintLabel.visibility = View.GONE
cardReaderDetailFirstHintNumberLabel.visibility = View.GONE
cardReaderDetailSecondHintLabel.visibility = View.GONE
cardReaderDetailSecondHintNumberLabel.visibility = View.GONE
cardReaderDetailThirdHintLabel.visibility = View.GONE
cardReaderDetailThirdHintNumberLabel.visibility = View.GONE
UiHelpers.setTextOrHide(cardReaderDetailConnectBtn, state.cancelBtnLabel)
cardReaderDetailConnectBtn.setOnClickListener { state.onCancelClicked.invoke() }
cardReaderDetailLearnMoreTv.root.visibility = View.GONE
}
}
}
}
}
Expand All @@ -192,7 +212,10 @@ class CardReaderDetailFragment : BaseFragment(R.layout.fragment_card_reader_deta

private fun makeStateVisible(binding: FragmentCardReaderDetailBinding, state: ViewState) {
UiHelpers.updateVisibility(binding.readerConnectedState.root, state is ConnectedState)
UiHelpers.updateVisibility(binding.readerDisconnectedState.root, state is NotConnectedState)
UiHelpers.updateVisibility(
binding.readerDisconnectedState.root,
state is NotConnectedState || state is ReconnectingState
)
UiHelpers.updateVisibility(binding.readerConnectedLoading, state is Loading)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.woocommerce.android.cardreader.connection.CardReader
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connecting
import com.woocommerce.android.cardreader.connection.CardReaderStatus.NotConnected
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Reconnecting
import com.woocommerce.android.cardreader.connection.ReaderType
import com.woocommerce.android.cardreader.connection.event.CardReaderBatteryStatus
import com.woocommerce.android.cardreader.connection.event.CardReaderBatteryStatus.StatusChanged
Expand Down Expand Up @@ -86,6 +87,9 @@ class CardReaderDetailViewModel @Inject constructor(
)
handleNotConnectedState()
}
Reconnecting -> {
handleReconnectingState()
}
}
}
}
Expand Down Expand Up @@ -134,6 +138,16 @@ class CardReaderDetailViewModel @Inject constructor(
NotConnectedState(onPrimaryActionClicked = ::onConnectBtnClicked, onLearnMoreClicked = ::onLearnMoreClicked)
}

private fun handleReconnectingState() {
viewState.value = ViewState.ReconnectingState(
onCancelClicked = ::onCancelReconnectionClicked
)
}

private fun onCancelReconnectionClicked() {
cardReaderManager.cancelReconnection()
}

private fun cancelConnectedScopeJobs() {
if (::softwareUpdateAvailabilityJob.isInitialized) softwareUpdateAvailabilityJob.cancel()
if (::batteryStatusUpdateJob.isInitialized) batteryStatusUpdateJob.cancel()
Expand Down Expand Up @@ -307,6 +321,16 @@ class CardReaderDetailViewModel @Inject constructor(
}

object Loading : ViewState()

data class ReconnectingState(
val onCancelClicked: (() -> Unit),
) : ViewState() {
val headerLabel = UiStringRes(R.string.card_reader_detail_reconnecting_header)

@DrawableRes
val illustration = R.drawable.img_card_reader_not_connected
val cancelBtnLabel = UiStringRes(R.string.card_reader_detail_reconnecting_cancel)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class WooPosCardReaderFacade @Inject constructor(
cardReaderManager.disconnectReader()
}

fun cancelReconnection() {
cardReaderManager.cancelReconnection()
}

@Suppress("DEPRECATION")
private fun startActivity(intent: Intent) {
val options = ActivityOptionsCompat.makeCustomAnimation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,15 +309,11 @@ private fun CardReaderStatusButton(
when (status) {
WooPosCardReaderStatus.Connected -> WooPosTheme.colors.success
WooPosCardReaderStatus.NotConnected -> WooPosTheme.colors.alert
WooPosCardReaderStatus.Reconnecting -> WooPosTheme.colors.alert
}
}

val title = stringResource(
id = when (state) {
WooPosCardReaderStatus.Connected -> WooPosCardReaderStatus.Connected.title
WooPosCardReaderStatus.NotConnected -> WooPosCardReaderStatus.NotConnected.title
}
)
val title = stringResource(id = state.title)

val borderColor by transition.animateColor(
transitionSpec = { tween(durationMillis = animationDuration) },
Expand All @@ -326,6 +322,7 @@ private fun CardReaderStatusButton(
when (status) {
WooPosCardReaderStatus.Connected -> Color.Transparent
WooPosCardReaderStatus.NotConnected -> MaterialTheme.colorScheme.primary
WooPosCardReaderStatus.Reconnecting -> WooPosTheme.colors.alert
}
}

Expand Down Expand Up @@ -408,6 +405,9 @@ private fun getToolbarAccessibilityLabels(
WooPosCardReaderStatus.NotConnected -> stringResource(
id = R.string.woopos_floating_toolbar_card_reader_not_connected_status_content_description
)
WooPosCardReaderStatus.Reconnecting -> stringResource(
id = R.string.woopos_reader_reconnecting
)
}
val floatingToolbarMenuOverlayContentDescription = when (menuCardDisabled) {
true -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ data class WooPosHomeFloatingToolbarState(
sealed class WooPosCardReaderStatus(@StringRes val title: Int) {
data object NotConnected : WooPosCardReaderStatus(title = R.string.woopos_reader_disconnected)
data object Connected : WooPosCardReaderStatus(title = R.string.woopos_reader_connected)
data object Reconnecting : WooPosCardReaderStatus(title = R.string.woopos_reader_reconnecting)
}

sealed class Menu {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.woocommerce.android.cardreader.connection.CardReaderStatus
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connecting
import com.woocommerce.android.cardreader.connection.CardReaderStatus.NotConnected
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Reconnecting
import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade
import com.woocommerce.android.ui.woopos.home.ChildToParentEvent
import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender
Expand Down Expand Up @@ -128,12 +129,17 @@ class WooPosHomeFloatingToolbarViewModel @Inject constructor(
cardReaderFacade.connectToReader()
}
}

WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Reconnecting -> {
cardReaderFacade.cancelReconnection()
}
}
}

private fun mapCardReaderStatusToUiState(status: CardReaderStatus) = when (status) {
is Connected -> WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected
is NotConnected, Connecting -> WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.NotConnected
Reconnecting -> WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Reconnecting
}

private val toolbarMenuItems by lazy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.woocommerce.android.WooException
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connected
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Connecting
import com.woocommerce.android.cardreader.connection.CardReaderStatus.NotConnected
import com.woocommerce.android.cardreader.connection.CardReaderStatus.Reconnecting
import com.woocommerce.android.model.Order
import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam.PaymentOrRefund
import com.woocommerce.android.ui.payments.cardreader.payment.controller.CardReaderPaymentController
Expand Down Expand Up @@ -122,6 +123,10 @@ class WooPosTotalsViewModel @Inject constructor(
cancelPaymentAction()
}

Reconnecting -> {
// We start payment right away so this state not worth handling
}

is Connected -> {
val state = uiState.value
if (state !is WooPosTotalsViewState.Checkout) return@collect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ class WooPosSettingsHardwareCardReaderViewModel @Inject constructor(
currentSoftwareUpdateAvailable = false
WooPosSettingsHardwareCardReaderUiState.Disconnected
}

CardReaderStatus.Reconnecting -> {
// Keep current state while SDK attempts to reconnect
_uiState.value
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1634,6 +1634,8 @@
Card Reader Detail
-->
<string name="card_reader_detail_not_connected_header">Connect your card reader</string>
<string name="card_reader_detail_reconnecting_header">Reconnecting to card reader…</string>
<string name="card_reader_detail_reconnecting_cancel">Cancel reconnection</string>
<string name="card_reader_detail_learn_more">&lt;a href=\'\'&gt;Learn more&lt;/a&gt; about accepting mobile payments and ordering card readers</string>
<string name="card_reader_detail_not_connected_first_hint_label">Make sure card reader is charged</string>
<string name="card_reader_detail_not_connected_second_hint_label">Turn card reader on and place it next to mobile device</string>
Expand Down Expand Up @@ -3590,6 +3592,7 @@

<string name="woopos_reader_connected">Reader connected</string>
<string name="woopos_reader_disconnected">Connect your reader</string>
<string name="woopos_reader_reconnecting">Reconnecting…</string>
<string name="woopos_checkout_button">Check out</string>
<string name="woopos_remove_item_button_from_cart_content_description">Remove %s from cart</string>
<string name="woopos_product_item_content_description">Product %s, Price %s</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailVie
import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailViewModel.ViewState.ConnectedState
import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailViewModel.ViewState.Loading
import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailViewModel.ViewState.NotConnectedState
import com.woocommerce.android.ui.payments.cardreader.detail.CardReaderDetailViewModel.ViewState.ReconnectingState
import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam
import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.STRIPE_EXTENSION_GATEWAY
import com.woocommerce.android.ui.payments.cardreader.onboarding.PluginType.WOOCOMMERCE_PAYMENTS
Expand Down Expand Up @@ -620,6 +621,33 @@ class CardReaderDetailViewModelTest : BaseUnitTest() {
.isEqualTo(AppUrls.STRIPE_LEARN_MORE_ABOUT_PAYMENTS)
}

@Test
fun `given reconnecting state, when view model init, then should emit reconnecting view state`() {
// GIVEN
val status = MutableStateFlow(CardReaderStatus.Reconnecting)
whenever(cardReaderManager.readerStatus).thenReturn(status)

// WHEN
val viewModel = createViewModel()

// THEN
assertThat(viewModel.viewStateData.value).isInstanceOf(ReconnectingState::class.java)
}

@Test
fun `given reconnecting state, when cancel clicked, then should call cancelReconnection`() {
// GIVEN
val status = MutableStateFlow(CardReaderStatus.Reconnecting)
whenever(cardReaderManager.readerStatus).thenReturn(status)
val viewModel = createViewModel()

// WHEN
(viewModel.viewStateData.value as ReconnectingState).onCancelClicked.invoke()

// THEN
verify(cardReaderManager).cancelReconnection()
}

private fun verifyNotConnectedState(viewModel: CardReaderDetailViewModel) {
val state = viewModel.viewStateData.value as NotConnectedState
assertThat(state.headerLabel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,31 @@ class WooPosHomeFloatingToolbarViewModelTest {
assertThat(viewModel.state.value.menu).isEqualTo(WooPosHomeFloatingToolbarState.Menu.Hidden)
}

@Test
fun `given card reader status is Reconnecting, when initialized, then state should be Reconnecting`() = runTest {
// GIVEN
whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Reconnecting))
val viewModel = createViewModel()

// THEN
assertThat(viewModel.state.value.cardReaderStatus)
.isEqualTo(WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Reconnecting)
}

@Test
fun `given card reader status is Reconnecting, when OnCardReaderStatusClicked, then cancelReconnection should be called`() =
runTest {
// GIVEN
whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Reconnecting))
val viewModel = createViewModel()

// WHEN
viewModel.onUiEvent(WooPosHomeFloatingToolbarUIEvent.OnCardReaderStatusClicked)

// THEN
verify(cardReaderFacade).cancelReconnection()
}

private fun createViewModel() = WooPosHomeFloatingToolbarViewModel(
cardReaderFacade,
childrenToParentEventSender,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ interface CardReaderManager {

fun startConnectionToReader(cardReader: CardReader, locationId: String)
suspend fun disconnectReader(): Boolean
fun cancelReconnection()

suspend fun collectPayment(paymentInfo: PaymentInfo): Flow<CardPaymentStatus>
suspend fun refundInteracPayment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object CardReaderManagerFactory {
UpdateErrorMapper(batteryLevelProvider),
terminalListener
)
val tapToPayReaderListener = TapToPayReaderListenerImpl(logWrapper)
val tapToPayReaderListener = TapToPayReaderListenerImpl(logWrapper, terminalListener)
val cardReaderConfigFactory = CardReaderConfigFactory()
val paymentUtils = PaymentUtils(logWrapper)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ sealed class CardReaderStatus {
}
data class Connected(val cardReader: CardReader) : CardReaderStatus()
data object Connecting : CardReaderStatus()
data object Reconnecting : CardReaderStatus()
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ internal class CardReaderManagerImpl(
return connectionManager.disconnectReader()
}

override fun cancelReconnection() {
if (!terminal.isInitialized()) error("Terminal not initialized")
connectionManager.cancelReconnection()
}

override suspend fun collectPayment(paymentInfo: PaymentInfo): Flow<CardPaymentStatus> {
resetBluetoothDisplayMessage()
return paymentManager.acceptPayment(paymentInfo)
Expand Down
Loading