diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt index 3d02cc9a830..ecb255ef55b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -132,7 +132,8 @@ object AppPrefs { CUSTOM_FIELDS_TOP_BANNER_DISMISSED, BLAZE_CAMPAIGN_SELECTED_OBJECTIVE, BLAZE_CAMPAIGN_OBJECTIVE_SWITCH_CHECKED, - IS_SITE_WPCOM_SUSPENDED + IS_SITE_WPCOM_SUSPENDED, + WOO_POS_PAYMENT_SOUND_ENABLED, } /** @@ -287,6 +288,10 @@ object AppPrefs { get() = getBoolean(DeletablePrefKey.IS_SITE_WPCOM_SUSPENDED, false) set(value) = setBoolean(DeletablePrefKey.IS_SITE_WPCOM_SUSPENDED, value) + var isWooPosPaymentSoundEnabled: Boolean + get() = getBoolean(DeletablePrefKey.WOO_POS_PAYMENT_SOUND_ENABLED, true) + set(value) = setBoolean(DeletablePrefKey.WOO_POS_PAYMENT_SOUND_ENABLED, value) + fun getProductSortingChoice(currentSiteId: Int) = getString(getProductSortingKey(currentSiteId)).orNullIfEmpty() fun setProductSortingChoice(currentSiteId: Int, value: String) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt index 569e8a02029..f9290a2a3f4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt @@ -39,6 +39,8 @@ class AppPrefsWrapper @Inject constructor() { var isSiteWPComSuspended by AppPrefs::isSiteWPComSuspended + var isWooPosPaymentSoundEnabled by AppPrefs::isWooPosPaymentSoundEnabled + fun getAppInstallationDate() = AppPrefs.installationDate fun getReceiptUrl(localSiteId: Int, remoteSiteId: Long, selfHostedSiteId: Long, orderId: Long) = 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 68abca2149b..c4af9eeba95 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 @@ -28,6 +28,7 @@ class WooPosHomeViewModel @Inject constructor( private val parentToChildrenEventSender: WooPosParentToChildrenEventSender, private val wooPosItemsNavigator: WooPosItemsNavigator, private val analyticsTracker: WooPosAnalyticsTracker, + private val appPrefsWrapper: AppPrefsWrapper, savedStateHandle: SavedStateHandle, ) : ViewModel() { private val _state = savedStateHandle.getStateFlow( @@ -219,7 +220,9 @@ class WooPosHomeViewModel @Inject constructor( wooPosItemsNavigator.sendNavigationEvent( WooPosItemsNavigator.WooPosItemsScreenNavigationEvent.NavigateBackToItemListScreen ) - _playChaChingEvent.emit("Cha-Ching") + if (appPrefsWrapper.isWooPosPaymentSoundEnabled) { + _playChaChingEvent.emit("Cha-Ching") + } } _state.value = _state.value.copy( screenPositionState = ScreenPositionState.Checkout.FullScreenTotals diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbar.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbar.kt index 104d87a1543..a1d8c44b711 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbar.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbar.kt @@ -11,8 +11,10 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer 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 @@ -23,6 +25,7 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.Switch import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -32,6 +35,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -274,6 +278,9 @@ private fun PopUpMenuItem( menuItem: Menu.MenuItem, onClick: (Menu.MenuItem) -> Unit ) { + var isChaChingEnabled by remember { + mutableStateOf(menuItem is Menu.MenuItem.Toggleable && menuItem.isToggled) + } TextButton(onClick = { onClick(menuItem) }) { Spacer(modifier = Modifier.width(WooPosSpacing.Medium.value.toAdaptivePadding())) Icon( @@ -293,10 +300,46 @@ private fun PopUpMenuItem( maxLines = 1, overflow = TextOverflow.Ellipsis, ) + if (menuItem is Menu.MenuItem.Toggleable) { + ToggleSwitch( + isChecked = isChaChingEnabled, + onToggleChange = { + isChaChingEnabled = it + onClick(menuItem.copy(isToggled = !menuItem.isToggled)) + } + ) + } Spacer(modifier = Modifier.width(WooPosSpacing.Medium.value.toAdaptivePadding())) } } +@Composable +fun ToggleSwitch( + isChecked: Boolean, + onToggleChange: (Boolean) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + WooPosText( + modifier = Modifier + .padding(vertical = WooPosSpacing.Small.value.toAdaptivePadding()) + .weight(1f), + text = stringResource(id = R.string.woopos_payment_success_sound_setting_title), + style = WooPosTypography.BodyMedium, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Switch( + checked = isChecked, + onCheckedChange = onToggleChange + ) + } +} + @Composable private fun CardReaderStatusButton( modifier: Modifier, @@ -484,15 +527,20 @@ fun PreviewWooPosFloatingToolbarStatusConnectedWithMenu() { cardReaderStatus = WooPosCardReaderStatus.Connected, menu = Menu.Visible( listOf( - Menu.MenuItem( + Menu.MenuItem.Toggleable( + title = R.string.woopos_cart_title, + icon = R.drawable.woo_pos_info_ic, + isToggled = true + ), + Menu.MenuItem.Standard( title = R.string.woopos_documentation_title, icon = R.drawable.woo_pos_info_ic ), - Menu.MenuItem( + Menu.MenuItem.Standard( title = R.string.woopos_exit_confirmation_title, icon = R.drawable.ic_woo_pos_exit, ), - Menu.MenuItem( + Menu.MenuItem.Standard( title = R.string.woopos_get_support_title, icon = R.drawable.woopos_ic_get_support, ), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarState.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarState.kt index dd3ec45ab82..e451591e450 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarState.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/home/toolbar/WooPosToolbarState.kt @@ -17,9 +17,20 @@ data class WooPosToolbarState( data object Hidden : Menu() data class Visible(val items: List) : Menu() - data class MenuItem( - @StringRes val title: Int, - @DrawableRes val icon: Int, - ) + sealed class MenuItem( + @StringRes open val title: Int, + @DrawableRes open val icon: Int + ) { + data class Standard( + @StringRes override val title: Int, + @DrawableRes override val icon: Int + ) : MenuItem(title, icon) + + data class Toggleable( + @StringRes override val title: Int, + @DrawableRes override val icon: Int, + val isToggled: Boolean + ) : MenuItem(title, icon) + } } } 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 335f52c3316..e58a8758166 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 @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.home.toolbar import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.AppUrls.WOO_POS_DOCUMENTATION_URL import com.woocommerce.android.R import com.woocommerce.android.cardreader.connection.CardReaderStatus @@ -39,6 +40,7 @@ class WooPosToolbarViewModel @Inject constructor( private val networkStatus: WooPosNetworkStatus, private val resourceProvider: ResourceProvider, private val analyticsTracker: WooPosAnalyticsTracker, + private val appPrefsWrapper: AppPrefsWrapper, ) : ViewModel() { private val _state = MutableStateFlow( WooPosToolbarState( @@ -70,8 +72,15 @@ class WooPosToolbarViewModel @Inject constructor( when (event) { is OnToolbarMenuClicked -> { + val updatedToolBarMenuItems = toolbarMenuItems.map { + if (it is WooPosToolbarState.Menu.MenuItem.Toggleable) { + it.copy(isToggled = appPrefsWrapper.isWooPosPaymentSoundEnabled) + } else { + it + } + } _state.value = currentState.copy( - menu = WooPosToolbarState.Menu.Visible(toolbarMenuItems) + menu = WooPosToolbarState.Menu.Visible(updatedToolBarMenuItems) ) } @@ -86,7 +95,9 @@ class WooPosToolbarViewModel @Inject constructor( } private fun handleMenuItemClicked(event: MenuItemClicked) { - hideMenu() + if (event.menuItem !is WooPosToolbarState.Menu.MenuItem.Toggleable) { + hideMenu() + } when (event.menuItem.title) { R.string.woopos_get_support_title -> { @@ -95,17 +106,25 @@ class WooPosToolbarViewModel @Inject constructor( analyticsTracker.track(GetSupportTapped) } } + R.string.woopos_exit_confirmation_title -> viewModelScope.launch { childrenToParentEventSender.sendToParent(ChildToParentEvent.ExitPosClicked) analyticsTracker.track(ExitTapped) } + R.string.woopos_documentation_title -> { viewModelScope.launch { _openUrlEvent.emit(WOO_POS_DOCUMENTATION_URL) analyticsTracker.track(ViewDocsTapped) } } + + R.string.woopos_payment_success_sound_setting_title -> { + viewModelScope.launch { + appPrefsWrapper.isWooPosPaymentSoundEnabled = !appPrefsWrapper.isWooPosPaymentSoundEnabled + } + } } } @@ -120,6 +139,7 @@ class WooPosToolbarViewModel @Inject constructor( cardReaderFacade.disconnectFromReader() } } + WooPosToolbarState.WooPosCardReaderStatus.NotConnected -> { if (!networkStatus.isConnected()) { viewModelScope.launch { @@ -143,20 +163,23 @@ class WooPosToolbarViewModel @Inject constructor( } } - private companion object { - val toolbarMenuItems = listOf( - WooPosToolbarState.Menu.MenuItem( - title = R.string.woopos_documentation_title, - icon = R.drawable.woo_pos_info_ic, - ), - WooPosToolbarState.Menu.MenuItem( - title = R.string.woopos_get_support_title, - icon = R.drawable.woopos_ic_get_support, - ), - WooPosToolbarState.Menu.MenuItem( - title = R.string.woopos_exit_confirmation_title, - icon = R.drawable.ic_woo_pos_exit, - ), - ) - } + private val toolbarMenuItems = listOf( + WooPosToolbarState.Menu.MenuItem.Toggleable( + title = R.string.woopos_payment_success_sound_setting_title, + icon = R.drawable.woo_pos_info_ic, + isToggled = appPrefsWrapper.isWooPosPaymentSoundEnabled + ), + WooPosToolbarState.Menu.MenuItem.Standard( + title = R.string.woopos_documentation_title, + icon = R.drawable.woo_pos_info_ic, + ), + WooPosToolbarState.Menu.MenuItem.Standard( + title = R.string.woopos_get_support_title, + icon = R.drawable.woopos_ic_get_support, + ), + WooPosToolbarState.Menu.MenuItem.Standard( + title = R.string.woopos_exit_confirmation_title, + icon = R.drawable.ic_woo_pos_exit, + ), + ) } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 3e1f5618560..b74b044be10 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -4279,6 +4279,7 @@ Product in cart %s, Price %s Cart Clear + Cha-Ching Sound Showing simple, variable and virtual products only Only simple physical, variable and virtual products are compatible with POS right now. Other product types will become available in future updates. diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt index 6aa5e55b0c1..12afdc36db2 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/woopos/home/WooPosHomeViewModelTest.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.ui.woopos.home import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.SavedStateHandle +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.R import com.woocommerce.android.ui.woopos.home.ParentToChildrenEvent.OrderSuccessfullyPaid.PaymentMethod import com.woocommerce.android.ui.woopos.home.WooPosHomeUIEvent.ExitPosClicked @@ -38,6 +39,7 @@ class WooPosHomeViewModelTest { private val parentToChildrenEventSender: WooPosParentToChildrenEventSender = mock() private val wooPosItemsNavigator: WooPosItemsNavigator = mock() private val analyticsTracker: WooPosAnalyticsTracker = mock() + private val appPrefsWrapper: AppPrefsWrapper = mock() @Test fun `given state checkout, when SystemBackClicked passed, then BackFromCheckoutToCartClicked event should be sent`() = @@ -459,6 +461,7 @@ class WooPosHomeViewModelTest { parentToChildrenEventSender, wooPosItemsNavigator, analyticsTracker, + appPrefsWrapper = appPrefsWrapper, SavedStateHandle() ) } 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 cc31a940c5c..49515837ce9 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 @@ -1,6 +1,7 @@ package com.woocommerce.android.ui.woopos.home.toolbar import app.cash.turbine.test +import com.woocommerce.android.AppPrefsWrapper import com.woocommerce.android.AppUrls.WOO_POS_DOCUMENTATION_URL import com.woocommerce.android.R import com.woocommerce.android.cardreader.connection.CardReaderStatus @@ -40,6 +41,7 @@ class WooPosToolbarViewModelTest { private val networkStatus: WooPosNetworkStatus = mock() private val resourceProvider: ResourceProvider = mock() private val analyticsTracker: WooPosAnalyticsTracker = mock() + private val appPrefsWrapper: AppPrefsWrapper = mock() @Test fun `given card reader status is NotConnected, when initialized, then state should be NotConnected`() = runTest { @@ -87,15 +89,20 @@ class WooPosToolbarViewModelTest { .isEqualTo( WooPosToolbarState.Menu.Visible( listOf( - WooPosToolbarState.Menu.MenuItem( + WooPosToolbarState.Menu.MenuItem.Toggleable( + title = R.string.woopos_payment_success_sound_setting_title, + icon = R.drawable.woo_pos_info_ic, + isToggled = false + ), + WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_documentation_title, icon = R.drawable.woo_pos_info_ic, ), - WooPosToolbarState.Menu.MenuItem( + WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_get_support_title, icon = R.drawable.woopos_ic_get_support, ), - WooPosToolbarState.Menu.MenuItem( + WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_exit_confirmation_title, icon = R.drawable.ic_woo_pos_exit, ), @@ -136,7 +143,7 @@ class WooPosToolbarViewModelTest { fun `when MenuItemClicked with ExitPosClicked, then ExitPosClicked event should be sent`() = runTest { // GIVEN val viewModel = createViewModel() - val menuItem = WooPosToolbarState.Menu.MenuItem( + val menuItem = WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_exit_confirmation_title, icon = R.drawable.ic_woo_pos_exit ) @@ -184,7 +191,7 @@ class WooPosToolbarViewModelTest { viewModel.onUiEvent( WooPosToolbarUIEvent.MenuItemClicked( - WooPosToolbarState.Menu.MenuItem( + WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_get_support_title, icon = R.drawable.woopos_ic_get_support, ) @@ -232,7 +239,7 @@ class WooPosToolbarViewModelTest { fun `when Documentation MenuItemClicked, then openUrlEvent should be emitted with proper url`() = runTest { // GIVEN val viewModel = createViewModel() - val menuItem = WooPosToolbarState.Menu.MenuItem( + val menuItem = WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_documentation_title, icon = R.drawable.ic_help_24dp ) @@ -252,7 +259,7 @@ class WooPosToolbarViewModelTest { @Test fun `when get Support is clicked, then should track analytics event`() = runTest { val viewModel = createViewModel() - val menuItem = WooPosToolbarState.Menu.MenuItem( + val menuItem = WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_get_support_title, icon = R.drawable.ic_help_24dp ) @@ -264,7 +271,7 @@ class WooPosToolbarViewModelTest { @Test fun `when View Documentation is clicked, then should track analytics event`() = runTest { val viewModel = createViewModel() - val menuItem = WooPosToolbarState.Menu.MenuItem( + val menuItem = WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_documentation_title, icon = R.drawable.ic_info_outline_20dp ) @@ -276,7 +283,7 @@ class WooPosToolbarViewModelTest { @Test fun `when Exit menu item is clicked, then should track analytics event`() = runTest { val viewModel = createViewModel() - val menuItem = WooPosToolbarState.Menu.MenuItem( + val menuItem = WooPosToolbarState.Menu.MenuItem.Standard( title = R.string.woopos_exit_confirmation_title, icon = R.drawable.ic_woo_pos_exit ) @@ -292,5 +299,6 @@ class WooPosToolbarViewModelTest { networkStatus, resourceProvider, analyticsTracker, + appPrefsWrapper = appPrefsWrapper ) }