Skip to content
Merged
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 @@ -305,7 +305,7 @@ private fun CardReaderStatusButton(
label = "IllustrationColorTransition"
) { status ->
when (status) {
WooPosCardReaderStatus.Connected -> WooPosTheme.colors.success
is WooPosCardReaderStatus.Connected -> WooPosTheme.colors.success
WooPosCardReaderStatus.NotConnected -> WooPosTheme.colors.alert
WooPosCardReaderStatus.Reconnecting -> WooPosTheme.colors.alert
}
Expand All @@ -318,7 +318,7 @@ private fun CardReaderStatusButton(
label = "BorderColorTransition"
) { status ->
when (status) {
WooPosCardReaderStatus.Connected -> Color.Transparent
is WooPosCardReaderStatus.Connected -> Color.Transparent
WooPosCardReaderStatus.NotConnected -> MaterialTheme.colorScheme.primary
WooPosCardReaderStatus.Reconnecting -> WooPosTheme.colors.alert
}
Expand Down Expand Up @@ -356,6 +356,11 @@ private fun CardReaderStatusButton(
modifier = Modifier.animateContentSize(),
title = title,
)

if (state is WooPosCardReaderStatus.Connected) {
BatteryWarningIcon(batteryState = state.batteryState)
}

Spacer(modifier = Modifier.width(WooPosSpacing.Medium.value))
}
}
Expand Down Expand Up @@ -387,6 +392,29 @@ private fun Circle(
)
}

@Composable
private fun BatteryWarningIcon(batteryState: WooPosHomeFloatingToolbarState.BatteryState) {
when (batteryState) {
WooPosHomeFloatingToolbarState.BatteryState.NOMINAL -> { }
WooPosHomeFloatingToolbarState.BatteryState.LOW -> {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_woo_pos_battery_low),
contentDescription = stringResource(R.string.woopos_battery_low),
tint = WooPosTheme.colors.alert,
modifier = Modifier.size(20.dp)
)
}
WooPosHomeFloatingToolbarState.BatteryState.CRITICAL -> {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_woo_pos_battery_critical),
contentDescription = stringResource(R.string.woopos_battery_critical),
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(20.dp)
)
}
}
}

@Composable
private fun getToolbarAccessibilityLabels(
cardReaderStatus: WooPosCardReaderStatus,
Expand All @@ -397,7 +425,7 @@ private fun getToolbarAccessibilityLabels(
)

val cardReaderStatusContentDescription = when (cardReaderStatus) {
WooPosCardReaderStatus.Connected -> stringResource(
is WooPosCardReaderStatus.Connected -> stringResource(
id = R.string.woopos_floating_toolbar_card_reader_connected_status_content_description
)

Expand Down Expand Up @@ -475,7 +503,7 @@ fun PreviewWooPosFloatingToolbarStatusConnectedWithMenu() {
val state = remember {
mutableStateOf(
WooPosHomeFloatingToolbarState(
cardReaderStatus = WooPosCardReaderStatus.Connected,
cardReaderStatus = WooPosCardReaderStatus.Connected(),
menu = Menu.Visible(
listOf(
Menu.MenuItem(
Expand All @@ -498,6 +526,38 @@ fun PreviewWooPosFloatingToolbarStatusConnectedWithMenu() {
Preview(state)
}

@WooPosPreview
@Composable
fun PreviewWooPosFloatingToolbarStatusConnectedBatteryLow() {
val state = remember {
mutableStateOf(
WooPosHomeFloatingToolbarState(
cardReaderStatus = WooPosCardReaderStatus.Connected(
batteryState = WooPosHomeFloatingToolbarState.BatteryState.LOW
),
menu = Menu.Hidden
)
)
}
Preview(state)
}

@WooPosPreview
@Composable
fun PreviewWooPosFloatingToolbarStatusConnectedBatteryCritical() {
val state = remember {
mutableStateOf(
WooPosHomeFloatingToolbarState(
cardReaderStatus = WooPosCardReaderStatus.Connected(
batteryState = WooPosHomeFloatingToolbarState.BatteryState.CRITICAL
),
menu = Menu.Hidden
)
)
}
Preview(state)
}

@Composable
private fun Preview(state: MutableState<WooPosHomeFloatingToolbarState>) {
WooPosTheme {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ 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 class Connected(
val batteryState: BatteryState = BatteryState.NOMINAL
) : WooPosCardReaderStatus(title = R.string.woopos_reader_connected)
data object Reconnecting : WooPosCardReaderStatus(title = R.string.woopos_reader_reconnecting)
}

enum class BatteryState {
NOMINAL,
LOW,
CRITICAL
}

sealed class Menu {
data object Hidden : Menu()
data class Visible(val items: List<MenuItem>) : Menu()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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.event.BatteryStatus
import com.woocommerce.android.cardreader.connection.event.CardReaderBatteryStatus
import com.woocommerce.android.ciab.CIABSiteGateKeeper
import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade
import com.woocommerce.android.ui.woopos.cardreader.connection.WooPosCardReaderConnectionController
Expand All @@ -27,6 +29,7 @@ import com.woocommerce.android.viewmodel.ResourceProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand Down Expand Up @@ -55,10 +58,13 @@ class WooPosHomeFloatingToolbarViewModel @Inject constructor(

init {
viewModelScope.launch {
cardReaderFacade.readerStatus.collect {
_state.value = _state.value.copy(
cardReaderStatus = mapCardReaderStatusToUiState(it)
)
combine(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ I'm wondering if combine is the appropriate operator here. Battery status is dependent on reader status => battery is always unknown unless a reader is connected. Have you considered using flatMapLatest here? Something like:

  cardReaderFacade.readerStatus.flatMapLatest { readerStatus ->
      when (readerStatus) {
          is Connected -> cardReaderFacade.batteryStatus.map {
              WooPosCardReaderStatus.Connected(batteryState = mapBatteryState(it))
          }
          is NotConnected, Connecting -> flowOf(WooPosCardReaderStatus.NotConnected)
          Reconnecting -> flowOf(WooPosCardReaderStatus.Reconnecting)
      }
  }

cardReaderFacade.readerStatus,
cardReaderFacade.batteryStatus
) { readerStatus, batteryStatus ->
mapCardReaderStatusToUiState(readerStatus, batteryStatus)
}.collect { cardReaderStatus ->
_state.value = _state.value.copy(cardReaderStatus = cardReaderStatus)
}
}
}
Expand Down Expand Up @@ -125,7 +131,7 @@ class WooPosHomeFloatingToolbarViewModel @Inject constructor(

private fun handleOnCardReaderStatusClicked() {
when (_state.value.cardReaderStatus) {
WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected -> {
is WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected -> {
viewModelScope.launch {
controller.disconnect()
}
Expand Down Expand Up @@ -153,12 +159,29 @@ class WooPosHomeFloatingToolbarViewModel @Inject constructor(
}
}

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

private fun mapBatteryState(status: CardReaderBatteryStatus): WooPosHomeFloatingToolbarState.BatteryState {
return when (status) {
is CardReaderBatteryStatus.StatusChanged -> when (status.batteryStatus) {
BatteryStatus.CRITICAL -> WooPosHomeFloatingToolbarState.BatteryState.CRITICAL
BatteryStatus.LOW -> WooPosHomeFloatingToolbarState.BatteryState.LOW
BatteryStatus.NOMINAL, BatteryStatus.UNKNOWN -> WooPosHomeFloatingToolbarState.BatteryState.NOMINAL
}
CardReaderBatteryStatus.Warning -> WooPosHomeFloatingToolbarState.BatteryState.LOW
CardReaderBatteryStatus.Unknown -> WooPosHomeFloatingToolbarState.BatteryState.NOMINAL
}
}

private val toolbarMenuItems by lazy {
buildList {
if (ciabSiteGateKeeper.isCurrentSiteCIAB()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M440,560L520,560L520,320L440,320L440,560ZM480,720Q497,720 508.5,708.5Q520,697 520,680Q520,663 508.5,651.5Q497,640 480,640Q463,640 451.5,651.5Q440,663 440,680Q440,697 451.5,708.5Q463,720 480,720ZM320,880Q303,880 291.5,868.5Q280,857 280,840L280,200Q280,183 291.5,171.5Q303,160 320,160L400,160L400,80L560,80L560,160L640,160Q657,160 668.5,171.5Q680,183 680,200L680,840Q680,857 668.5,868.5Q657,880 640,880L320,880ZM360,800L600,800L600,240L360,240L360,800ZM360,800L360,800L600,800L600,800L360,800Z" />
</vector>
9 changes: 9 additions & 0 deletions WooCommerce/src/main/res/drawable/ic_woo_pos_battery_low.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M320,880Q303,880 291.5,868.5Q280,857 280,840L280,200Q280,183 291.5,171.5Q303,160 320,160L400,160L400,80L560,80L560,160L640,160Q657,160 668.5,171.5Q680,183 680,200L680,840Q680,857 668.5,868.5Q657,880 640,880L320,880ZM360,720L600,720L600,240L360,240L360,720Z" />
</vector>
2 changes: 2 additions & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3646,6 +3646,8 @@
<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_battery_low">Card reader battery low</string>
<string name="woopos_battery_critical">Card reader battery critical</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 @@ -2,19 +2,23 @@ package com.woocommerce.android.ui.woopos.home.toolbar

import com.woocommerce.android.R
import com.woocommerce.android.cardreader.connection.CardReaderStatus
import com.woocommerce.android.cardreader.connection.event.BatteryStatus
import com.woocommerce.android.cardreader.connection.event.CardReaderBatteryStatus
import com.woocommerce.android.ciab.CIABSiteGateKeeper
import com.woocommerce.android.ui.woopos.cardreader.WooPosCardReaderFacade
import com.woocommerce.android.ui.woopos.cardreader.connection.WooPosCardReaderConnectionController
import com.woocommerce.android.ui.woopos.cardreader.connection.WooPosCardReaderConnectionControllerFactory
import com.woocommerce.android.ui.woopos.home.ChildToParentEvent
import com.woocommerce.android.ui.woopos.home.WooPosChildrenToParentEventSender
import com.woocommerce.android.ui.woopos.home.toolbar.WooPosHomeFloatingToolbarState.BatteryState
import com.woocommerce.android.ui.woopos.util.WooPosCoroutineTestRule
import com.woocommerce.android.ui.woopos.util.WooPosNetworkStatus
import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsEvent.Event.ExitTapped
import com.woocommerce.android.ui.woopos.util.analytics.WooPosAnalyticsTracker
import com.woocommerce.android.viewmodel.ResourceProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
Expand All @@ -33,6 +37,7 @@ class WooPosHomeFloatingToolbarViewModelTest {

private val cardReaderFacade: WooPosCardReaderFacade = mock {
onBlocking { readerStatus }.thenReturn(MutableStateFlow(CardReaderStatus.NotConnected()))
onBlocking { batteryStatus }.thenReturn(flowOf(CardReaderBatteryStatus.Unknown))
}
private val childrenToParentEventSender: WooPosChildrenToParentEventSender = mock()
private val networkStatus: WooPosNetworkStatus = mock()
Expand Down Expand Up @@ -65,7 +70,7 @@ class WooPosHomeFloatingToolbarViewModelTest {

// THEN
assertThat(viewModel.state.value.cardReaderStatus)
.isEqualTo(WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected)
.isInstanceOf(WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected::class.java)
}

@Test
Expand Down Expand Up @@ -315,6 +320,92 @@ class WooPosHomeFloatingToolbarViewModelTest {
assertThat(items.any { it.title == R.string.woopos_bookings_title }).isFalse
}

@Test
fun `given connected with nominal battery, when initialized, then battery state should be NOMINAL`() = runTest {
// GIVEN
whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock())))
whenever(cardReaderFacade.batteryStatus).thenReturn(
flowOf(CardReaderBatteryStatus.StatusChanged(0.8f, BatteryStatus.NOMINAL, false))
)

// WHEN
val viewModel = createViewModel()

// THEN
val status = viewModel.state.value.cardReaderStatus
assertThat(status).isInstanceOf(WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected::class.java)
assertThat((status as WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected).batteryState)
.isEqualTo(BatteryState.NOMINAL)
}

@Test
fun `given connected with low battery, when initialized, then battery state should be LOW`() = runTest {
// GIVEN
whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock())))
whenever(cardReaderFacade.batteryStatus).thenReturn(
flowOf(CardReaderBatteryStatus.StatusChanged(0.2f, BatteryStatus.LOW, false))
)

// WHEN
val viewModel = createViewModel()

// THEN
val status = viewModel.state.value.cardReaderStatus
assertThat(status).isInstanceOf(WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected::class.java)
assertThat((status as WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected).batteryState)
.isEqualTo(BatteryState.LOW)
}

@Test
fun `given connected with critical battery, when initialized, then battery state should be CRITICAL`() = runTest {
// GIVEN
whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock())))
whenever(cardReaderFacade.batteryStatus).thenReturn(
flowOf(CardReaderBatteryStatus.StatusChanged(0.05f, BatteryStatus.CRITICAL, false))
)

// WHEN
val viewModel = createViewModel()

// THEN
val status = viewModel.state.value.cardReaderStatus
assertThat(status).isInstanceOf(WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected::class.java)
assertThat((status as WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected).batteryState)
.isEqualTo(BatteryState.CRITICAL)
}

@Test
fun `given connected with battery warning, when initialized, then battery state should be LOW`() = runTest {
// GIVEN
whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock())))
whenever(cardReaderFacade.batteryStatus).thenReturn(flowOf(CardReaderBatteryStatus.Warning))

// WHEN
val viewModel = createViewModel()

// THEN
val status = viewModel.state.value.cardReaderStatus
assertThat(status).isInstanceOf(WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected::class.java)
assertThat((status as WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected).batteryState)
.isEqualTo(BatteryState.LOW)
}

@Test
fun `given connected with unknown battery, when initialized, then battery state should be NOMINAL`() = runTest {
// GIVEN
whenever(cardReaderFacade.readerStatus).thenReturn(MutableStateFlow(CardReaderStatus.Connected(mock())))
whenever(cardReaderFacade.batteryStatus).thenReturn(flowOf(CardReaderBatteryStatus.Unknown))

// WHEN
val viewModel = createViewModel()

// THEN
val status = viewModel.state.value.cardReaderStatus
assertThat(status).isInstanceOf(WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected::class.java)
assertThat((status as WooPosHomeFloatingToolbarState.WooPosCardReaderStatus.Connected).batteryState)
.isEqualTo(BatteryState.NOMINAL)
}

private fun createViewModel() = WooPosHomeFloatingToolbarViewModel(
cardReaderFacade,
childrenToParentEventSender,
Expand Down