From 43ffd4ca58d4063175d976ec7ef6c966f75b8f4b Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 9 May 2024 17:08:30 +0100 Subject: [PATCH 01/15] Add a new feature flag for dynamic dashboard M2 --- .../main/kotlin/com/woocommerce/android/util/FeatureFlag.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt index e692f77f964..b0821a5c4f4 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt @@ -17,7 +17,8 @@ enum class FeatureFlag { NEW_SHIPPING_SUPPORT, DYNAMIC_DASHBOARD, APP_PASSWORD_TUTORIAL, - EOSL_M1; + EOSL_M1, + DYNAMIC_DASHBOARD_M2; fun isEnabled(context: Context? = null): Boolean { return when (this) { @@ -30,7 +31,8 @@ enum class FeatureFlag { WC_SHIPPING_BANNER, BETTER_CUSTOMER_SEARCH_M2, ORDER_CREATION_AUTO_TAX_RATE, - EOSL_M1 -> PackageUtils.isDebugBuild() + EOSL_M1, + DYNAMIC_DASHBOARD_M2 -> PackageUtils.isDebugBuild() DYNAMIC_DASHBOARD, CONNECTIVITY_TOOL, From ee0f775e9e91d355d5c3c49c1fe2148b00e778f2 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Thu, 9 May 2024 17:15:25 +0100 Subject: [PATCH 02/15] Update the widgets store to add new cards when enabled --- .../ui/dashboard/data/DashboardDataStore.kt | 26 +++++++++++++++++-- .../ui/dashboard/data/DashboardRepository.kt | 3 +-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt index c515586fbd3..edf6294c2b0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt @@ -23,7 +23,7 @@ class DashboardDataStore @Inject constructor( SiteComponentEntryPoint::class.java ).dashboardDataStore() - val dashboard: Flow = dataStore.data + val widgets: Flow> = dataStore.data .catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { @@ -40,6 +40,25 @@ class DashboardDataStore @Inject constructor( it } } + .map { + val widgets = it.widgetsList.toMutableList() + + // Add any new widgets that are not present in the saved configuration + if (supportedWidgets.size != widgets.size) { + supportedWidgets.filter { type -> + widgets.none { widget -> widget.type == type.name } + }.forEach { type -> + widgets.add( + DashboardWidgetDataModel.newBuilder() + .setType(type.name) + .setIsAdded(false) + .build() + ) + } + } + + return@map widgets + } suspend fun updateDashboard(dashboard: DashboardDataModel) { runCatching { @@ -49,10 +68,13 @@ class DashboardDataStore @Inject constructor( } } - private fun getDefaultWidgets() = DashboardWidget.Type.entries.map { + private fun getDefaultWidgets() = supportedWidgets.map { DashboardWidgetDataModel.newBuilder() .setType(it.name) .setIsAdded(true) .build() } + + // Use the feature flag [DYNAMIC_DASHBOARD_M2] to filter out unsupported widgets during development + private val supportedWidgets: List = DashboardWidget.Type.entries } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt index 4edd4edea0a..a8d70de26fd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt @@ -12,7 +12,6 @@ import dagger.hilt.android.scopes.ActivityRetainedScoped import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import javax.inject.Inject @@ -51,7 +50,7 @@ class DashboardRepository @Inject constructor( ) val widgets = combine( - dashboardDataStore.dashboard.map { it.widgetsList }, + dashboardDataStore.widgets, statsWidgetsStatus, blazeWidgetStatus, onboardingWidgetStatus From 1c4541ae28ae125499f8d30017ba152f5e2a671e Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 10 May 2024 10:33:54 +0100 Subject: [PATCH 03/15] Use the view's lifecycle owner instead of fragment's one --- .../android/ui/products/list/ProductListToolbarHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt index cc214cbc0eb..755e2a7975d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt @@ -86,7 +86,7 @@ class ProductListToolbarHelper @Inject constructor( this.viewModel = productListViewModel this.binding = binding - fragment.lifecycle.addObserver(this) + fragment.viewLifecycleOwner.lifecycle.addObserver(this) if (productListViewModel.isSearching()) { binding.productsSearchTabView.isVisible = true From 53d96e7ec8fdb092cde30c684340157c7c4b096a Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 10 May 2024 10:40:49 +0100 Subject: [PATCH 04/15] Remove the toolbar callback when the fragment's view is destroyed --- .../android/ui/products/list/ProductListToolbarHelper.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt index 755e2a7975d..521e5372713 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt @@ -39,6 +39,10 @@ class ProductListToolbarHelper @Inject constructor( private var scanBarcodeMenuItem: MenuItem? = null private var searchView: SearchView? = null + private val refreshOptionsMenuCallback = Runnable { + refreshOptionsMenu() + } + override fun onCreate(owner: LifecycleOwner) { (activity as FragmentActivity).onBackPressedDispatcher.addCallback( owner, @@ -98,6 +102,7 @@ class ProductListToolbarHelper @Inject constructor( override fun onDestroy(owner: LifecycleOwner) { disableSearchListeners() + binding?.toolbar?.removeCallbacks(refreshOptionsMenuCallback) listFragment = null searchMenuItem = null scanBarcodeMenuItem = null @@ -169,9 +174,7 @@ class ProductListToolbarHelper @Inject constructor( // We want to refresh the options menu after the toolbar has been inflated // Otherwise, logic in it will be executed before the toolbar is in restored state after configuration change - toolbar.post { - refreshOptionsMenu() - } + toolbar.post(refreshOptionsMenuCallback) } private fun refreshOptionsMenu() { From 7c3391b2b248e4c8cdacd4ab30606fa4be4172b8 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Fri, 10 May 2024 16:05:30 +0100 Subject: [PATCH 05/15] Introduce a common component to use as header of the filterable cards --- .../DashboardFilterableCardHeader.kt | 129 ++++++++++++++++++ WooCommerce/src/main/res/values/strings.xml | 2 + 2 files changed, 131 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFilterableCardHeader.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFilterableCardHeader.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFilterableCardHeader.kt new file mode 100644 index 00000000000..bb2621d15a8 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFilterableCardHeader.kt @@ -0,0 +1,129 @@ +package com.woocommerce.android.ui.dashboard + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.ContentAlpha +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.FilterList +import androidx.compose.runtime.Composable +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.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.woocommerce.android.R +import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground + +@Composable +fun DashboardFilterableCardHeader( + title: String, + currentFilter: T, + filterList: List, + onFilterSelected: (T) -> Unit, + modifier: Modifier = Modifier, + mapper: @Composable (T) -> String = { it.toString() } +) { + Row( + horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.minor_100)), + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.padding( + start = dimensionResource(id = R.dimen.major_100) + ) + ) { + Text( + text = title, + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onSurface + ) + + Text( + text = mapper(currentFilter), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium) + ) + + Spacer(modifier = Modifier.weight(1f)) + + Box { + var isMenuExpanded by remember { mutableStateOf(false) } + IconButton( + onClick = { isMenuExpanded = true } + ) { + Icon( + imageVector = Icons.Default.FilterList, + contentDescription = stringResource( + id = R.string.dashboard_filter_menu_content_description + ), + tint = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium) + ) + } + + DropdownMenu( + expanded = isMenuExpanded, + onDismissRequest = { isMenuExpanded = false }, + modifier = Modifier + .defaultMinSize(minWidth = 250.dp) + ) { + filterList.forEach { + DropdownMenuItem( + onClick = { + onFilterSelected(it) + isMenuExpanded = false + } + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.minor_100)) + ) { + Text(text = mapper(it)) + Spacer(modifier = Modifier.weight(1f)) + if (currentFilter == it) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = stringResource(id = androidx.compose.ui.R.string.selected), + tint = MaterialTheme.colors.primary + ) + } else { + Spacer(modifier = Modifier.size(dimensionResource(R.dimen.image_minor_50))) + } + } + } + } + } + } + } +} + +@Composable +@Preview +private fun DashboardFilterableCardHeaderPreview() { + val filters = remember { + listOf("Filter 1", "Filter 2", "Filter 3") + } + var currentFilter by remember { mutableStateOf("Filter 1") } + WooThemeWithBackground { + DashboardFilterableCardHeader( + title = "Title", + currentFilter = currentFilter, + filterList = filters, + onFilterSelected = { currentFilter = it }, + modifier = Modifier + ) + } +} diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 9c8e9495bb2..1aa7cfaea3d 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -371,6 +371,7 @@ Months Years Change date range button + Open filter dropdown Visitors Orders @@ -417,6 +418,7 @@ Unable to load data contact support.]]> + Status From b8867f0a2b09492d119c6f734ab9072873e04c69 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Mon, 13 May 2024 12:05:33 +0100 Subject: [PATCH 06/15] Add a release notes entry --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index a3391c01759..183f85f1b63 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,7 +1,7 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] 18.7 ----- - +- [*] Products: Fixed a bug that caused the app to crash sometimes during screen rotation [https://github.com/woocommerce/woocommerce-android/pull/11484] 18.6 ----- From e2dd7e7753b8bbbe5bd37f415f97bcddb8130f70 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 13 May 2024 15:59:33 +0200 Subject: [PATCH 07/15] Woo POS FF --- .../android/ui/woopos/WooPosFeatureFlagEnabled.kt | 8 ++++++++ .../kotlin/com/woocommerce/android/util/FeatureFlag.kt | 2 ++ 2 files changed, 10 insertions(+) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosFeatureFlagEnabled.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosFeatureFlagEnabled.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosFeatureFlagEnabled.kt new file mode 100644 index 00000000000..4ce93483481 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosFeatureFlagEnabled.kt @@ -0,0 +1,8 @@ +package com.woocommerce.android.ui.woopos + +import com.woocommerce.android.util.FeatureFlag +import javax.inject.Inject + +class WooPosFeatureFlagEnabled @Inject constructor() { + fun isEnabled() = FeatureFlag.WOO_POS.isEnabled() +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt index 261cffa4e29..8a6db970eff 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt @@ -6,6 +6,7 @@ import android.content.Context * "Feature flags" are used to hide in-progress features from release versions */ enum class FeatureFlag { + WOO_POS, DB_DOWNGRADE, MORE_MENU_INBOX, WC_SHIPPING_BANNER, @@ -28,6 +29,7 @@ enum class FeatureFlag { OTHER_PAYMENT_METHODS, MORE_MENU_INBOX, + WOO_POS, WC_SHIPPING_BANNER, BETTER_CUSTOMER_SEARCH_M2, ORDER_CREATION_AUTO_TAX_RATE, From bc8f3b63b3f5906a64742e4b0bde252e10a4163f Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 13 May 2024 16:46:55 +0200 Subject: [PATCH 08/15] POS button to MORE menu --- .../android/ui/moremenu/MoreMenuViewModel.kt | 10 ++++++++++ ...{WooPosFeatureFlagEnabled.kt => IsWooPosEnabled.kt} | 4 ++-- WooCommerce/src/main/res/values/strings.xml | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) rename WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/{WooPosFeatureFlagEnabled.kt => IsWooPosEnabled.kt} (52%) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt index 3736c1aa4fa..1a2fe616cf8 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt @@ -28,6 +28,7 @@ import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus import com.woocommerce.android.ui.payments.taptopay.isAvailable import com.woocommerce.android.ui.plans.domain.SitePlan import com.woocommerce.android.ui.plans.repository.SitePlanRepository +import com.woocommerce.android.ui.woopos.IsWooPosEnabled import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel @@ -57,6 +58,7 @@ class MoreMenuViewModel @Inject constructor( private val moreMenuNewFeatureHandler: MoreMenuNewFeatureHandler, private val tapToPayAvailabilityStatus: TapToPayAvailabilityStatus, private val isBlazeEnabled: IsBlazeEnabled, + private val isWooPosAvailable: IsWooPosEnabled, ) : ScopedViewModel(savedState) { val moreMenuViewState = combine( @@ -133,6 +135,14 @@ class MoreMenuViewModel @Inject constructor( icon = R.drawable.ic_more_menu_inbox, isEnabled = moreMenuRepository.isInboxEnabled(), onClick = ::onInboxButtonClick + ), + MenuUiButton( + title = R.string.more_menu_button_woo_pos, + description = R.string.more_menu_button_woo_pos_description, + icon = R.drawable.ic_more_menu_payments, + isEnabled = isWooPosAvailable(), + onClick = { + } ) ) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosFeatureFlagEnabled.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/IsWooPosEnabled.kt similarity index 52% rename from WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosFeatureFlagEnabled.kt rename to WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/IsWooPosEnabled.kt index 4ce93483481..9e7ef857473 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/WooPosFeatureFlagEnabled.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/IsWooPosEnabled.kt @@ -3,6 +3,6 @@ package com.woocommerce.android.ui.woopos import com.woocommerce.android.util.FeatureFlag import javax.inject.Inject -class WooPosFeatureFlagEnabled @Inject constructor() { - fun isEnabled() = FeatureFlag.WOO_POS.isEnabled() +class IsWooPosEnabled @Inject constructor() { + operator fun invoke() = FeatureFlag.WOO_POS.isEnabled() } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 1aa7cfaea3d..2a0023651b1 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -3660,6 +3660,9 @@ Settings Update your preferences + Woo Pos + Temporary entry point to POS mode + From 213de23a08fa5b0720472b68a40a5a8998b784a2 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 13 May 2024 16:52:03 +0200 Subject: [PATCH 09/15] Added tests --- .../android/ui/moremenu/MoreMenuViewModel.kt | 4 +-- .../ui/moremenu/MoreMenuViewModelTests.kt | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt index 1a2fe616cf8..b1d8dbd5646 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt @@ -58,7 +58,7 @@ class MoreMenuViewModel @Inject constructor( private val moreMenuNewFeatureHandler: MoreMenuNewFeatureHandler, private val tapToPayAvailabilityStatus: TapToPayAvailabilityStatus, private val isBlazeEnabled: IsBlazeEnabled, - private val isWooPosAvailable: IsWooPosEnabled, + private val isWooPosEnabled: IsWooPosEnabled, ) : ScopedViewModel(savedState) { val moreMenuViewState = combine( @@ -140,7 +140,7 @@ class MoreMenuViewModel @Inject constructor( title = R.string.more_menu_button_woo_pos, description = R.string.more_menu_button_woo_pos_description, icon = R.drawable.ic_more_menu_payments, - isEnabled = isWooPosAvailable(), + isEnabled = isWooPosEnabled(), onClick = { } ) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt index 7e0dbb0e4b6..e92b4c183bc 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt @@ -9,6 +9,7 @@ import com.woocommerce.android.ui.moremenu.domain.MoreMenuRepository import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus import com.woocommerce.android.ui.plans.domain.SitePlan import com.woocommerce.android.ui.plans.repository.SitePlanRepository +import com.woocommerce.android.ui.woopos.IsWooPosEnabled import com.woocommerce.android.util.captureValues import com.woocommerce.android.util.runAndCaptureValues import com.woocommerce.android.viewmodel.BaseUnitTest @@ -72,6 +73,9 @@ class MoreMenuViewModelTests : BaseUnitTest() { private val isBlazeEnabled: IsBlazeEnabled = mock { onBlocking { invoke() } doReturn true } + private val isWooPosEnabled: IsWooPosEnabled = mock { + on { invoke() } doReturn true + } private val blazeCampaignsStore: BlazeCampaignsStore = mock() @@ -92,6 +96,7 @@ class MoreMenuViewModelTests : BaseUnitTest() { blazeCampaignsStore = blazeCampaignsStore, tapToPayAvailabilityStatus = tapToPayAvailabilityStatus, isBlazeEnabled = isBlazeEnabled, + isWooPosEnabled = isWooPosEnabled, ) } @@ -402,4 +407,35 @@ class MoreMenuViewModelTests : BaseUnitTest() { assertThat(event).isEqualTo(MoreMenuViewModel.MoreMenuEvent.OpenBlazeCampaignListEvent) } + + @Test + fun `given isWooPosEnabled returns false, when building state, then WooPOS button is not displayed`() = + testBlocking { + // GIVEN + setup { + whenever(isWooPosEnabled.invoke()).thenReturn(false) + } + + // WHEN + val states = viewModel.moreMenuViewState.captureValues() + + // THEN + assertThat(states.last().generalMenuItems.first { it.title == R.string.more_menu_button_woo_pos }.isEnabled) + .isFalse() + } + + @Test + fun `given isWooPosEnabled returns true, when building state, then WooPOS button is displayed`() = testBlocking { + // GIVEN + setup { + whenever(isWooPosEnabled.invoke()).thenReturn(true) + } + + // WHEN + val states = viewModel.moreMenuViewState.captureValues() + + // THEN + assertThat(states.last().generalMenuItems.first { it.title == R.string.more_menu_button_woo_pos }.isEnabled) + .isTrue() + } } From c6199cc12a6af64b36a62d4abd0f3f2ce22e4f1a Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 13 May 2024 17:02:51 +0200 Subject: [PATCH 10/15] Dummy WooPosActivity with opening it from the fragment --- WooCommerce/src/main/AndroidManifest.xml | 54 +++++++++++-------- .../android/ui/moremenu/MoreMenuFragment.kt | 9 ++++ .../android/ui/moremenu/MoreMenuViewModel.kt | 2 + .../android/ui/woopos/root/WooPosActivity.kt | 29 ++++++++++ 4 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt diff --git a/WooCommerce/src/main/AndroidManifest.xml b/WooCommerce/src/main/AndroidManifest.xml index 8969e4cfb5a..6e75c9bba77 100644 --- a/WooCommerce/src/main/AndroidManifest.xml +++ b/WooCommerce/src/main/AndroidManifest.xml @@ -14,8 +14,10 @@ - - + + @@ -47,16 +49,17 @@ + android:windowSoftInputMode="adjustResize"> - @@ -65,19 +68,20 @@ - + - + - - - - + + + + - + + @@ -94,15 +98,15 @@ - - + + + android:exported="true" + android:theme="@style/LoginTheme"> @@ -155,7 +159,8 @@ + tools:replace="android:screenOrientation" /> + + android:exported="false" + android:permission="android.permission.BIND_JOB_SERVICE" /> - + - - + diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt index 9cdf4192c7a..d26af1b66ea 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.ui.moremenu +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -20,6 +21,7 @@ import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.ui.main.MainActivity import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.NavigateToSettingsEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.NavigateToSubscriptionsEvent +import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.NavigateToWooPosEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.OpenBlazeCampaignCreationEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.OpenBlazeCampaignListEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.StartSitePickerEvent @@ -30,6 +32,7 @@ import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.ViewP import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.ViewReviewsEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.ViewStoreEvent import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam +import com.woocommerce.android.ui.woopos.root.WooPosActivity import com.woocommerce.android.util.ChromeCustomTabUtils import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -99,9 +102,15 @@ class MoreMenuFragment : TopLevelFragment() { is ViewPayments -> navigateToPayments() is OpenBlazeCampaignCreationEvent -> openBlazeCreationFlow() is OpenBlazeCampaignListEvent -> openBlazeCampaignList() + is NavigateToWooPosEvent -> openWooPos() } } } + + private fun openWooPos() { + startActivity(Intent(requireContext(), WooPosActivity::class.java)) + } + private fun openBlazeCampaignList() { findNavController().navigateSafely( MoreMenuFragmentDirections.actionMoreMenuToBlazeCampaignListFragment() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt index b1d8dbd5646..81b54b2e4ce 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt @@ -142,6 +142,7 @@ class MoreMenuViewModel @Inject constructor( icon = R.drawable.ic_more_menu_payments, isEnabled = isWooPosEnabled(), onClick = { + triggerEvent(MoreMenuEvent.NavigateToWooPosEvent) } ) ) @@ -326,5 +327,6 @@ class MoreMenuViewModel @Inject constructor( object ViewReviewsEvent : MoreMenuEvent() object ViewInboxEvent : MoreMenuEvent() object ViewCouponsEvent : MoreMenuEvent() + object NavigateToWooPosEvent : MoreMenuEvent() } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt new file mode 100644 index 00000000000..274c599943b --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt @@ -0,0 +1,29 @@ +package com.woocommerce.android.ui.woopos.root + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +class WooPosActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + Box( + Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text( + text = "Woo POS!", + style = MaterialTheme.typography.h3, + color = MaterialTheme.colors.primary, + ) + } + } + } +} From fd623f017eee4fa8dd4d01605571c9cb415b055c Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 13 May 2024 14:03:13 +0200 Subject: [PATCH 11/15] Remove unused code --- .../com/woocommerce/android/extensions/SiteModelExt.kt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/SiteModelExt.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/SiteModelExt.kt index 227c7bb820c..178afc1b131 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/SiteModelExt.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/extensions/SiteModelExt.kt @@ -4,7 +4,6 @@ import android.text.TextUtils import com.woocommerce.android.ui.plans.domain.FREE_TRIAL_PLAN_ID import com.woocommerce.android.util.WooLog import org.wordpress.android.fluxc.model.SiteModel -import org.wordpress.android.fluxc.store.SiteStore.SiteVisibility import org.wordpress.android.fluxc.store.SiteStore.SiteVisibility.PUBLIC import org.wordpress.android.fluxc.utils.SiteUtils.getNormalizedTimezone import org.wordpress.android.fluxc.utils.extensions.slashJoin @@ -74,14 +73,5 @@ val SiteModel?.isFreeTrial: Boolean val SiteModel?.isSitePublic: Boolean get() = this?.let { !isWPComAtomic || publishedStatus == PUBLIC.value() } ?: false -val SiteModel?.isSitePrivate: Boolean - get() = this?.publishedStatus == SiteVisibility.PRIVATE.value() - val SiteModel.isEligibleForAI: Boolean get() = isWPComAtomic || planActiveFeatures.orEmpty().contains("ai-assistant") - -val SiteModel?.isWooExpressSiteReadyToUse: Boolean - get() = this?.isJetpackInstalled == true && - this.isJetpackConnected && - this.isWpComStore && - this.hasWooCommerce From 900d37e4c77101a9a13a3c07a138c60721bfb3cd Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Mon, 13 May 2024 14:03:28 +0200 Subject: [PATCH 12/15] Use the correct private site check --- .../android/ui/products/details/ProductDetailViewModel.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt index 5f064dd8b0b..bd85b2c1f8c 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModel.kt @@ -25,7 +25,6 @@ import com.woocommerce.android.extensions.fastStripHtml import com.woocommerce.android.extensions.getList import com.woocommerce.android.extensions.isEligibleForAI import com.woocommerce.android.extensions.isEmpty -import com.woocommerce.android.extensions.isSitePrivate import com.woocommerce.android.extensions.isSitePublic import com.woocommerce.android.extensions.orNullIfEmpty import com.woocommerce.android.extensions.removeItem @@ -171,7 +170,7 @@ class ProductDetailViewModel @Inject constructor( // view state for the product detail screen val productDetailViewStateData = LiveDataDelegate( savedState = savedState, - initialValue = ProductDetailViewState(areImagesAvailable = !selectedSite.get().isSitePrivate) + initialValue = ProductDetailViewState(areImagesAvailable = !selectedSite.get().isPrivate) ) { old, new -> if (old?.productDraft != new.productDraft || old?.draftPassword != new.draftPassword) { new.productDraft?.let { From fe6ed3b547dbfa94e4c578ae8d025f46713ce445 Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 14 May 2024 17:16:38 +0200 Subject: [PATCH 13/15] Revert last merge commit --- RELEASE-NOTES.txt | 2 +- WooCommerce/src/main/AndroidManifest.xml | 54 +++----- .../DashboardFilterableCardHeader.kt | 129 ------------------ .../ui/dashboard/data/DashboardDataStore.kt | 26 +--- .../ui/dashboard/data/DashboardRepository.kt | 3 +- .../android/ui/moremenu/MoreMenuFragment.kt | 9 -- .../android/ui/moremenu/MoreMenuViewModel.kt | 12 -- .../products/list/ProductListToolbarHelper.kt | 11 +- .../android/ui/woopos/IsWooPosEnabled.kt | 8 -- .../android/ui/woopos/root/WooPosActivity.kt | 29 ---- .../woocommerce/android/util/FeatureFlag.kt | 8 +- WooCommerce/src/main/res/values/strings.xml | 5 - .../ui/moremenu/MoreMenuViewModelTests.kt | 36 ----- 13 files changed, 33 insertions(+), 299 deletions(-) delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFilterableCardHeader.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/IsWooPosEnabled.kt delete mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 183f85f1b63..a3391c01759 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,7 +1,7 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] 18.7 ----- -- [*] Products: Fixed a bug that caused the app to crash sometimes during screen rotation [https://github.com/woocommerce/woocommerce-android/pull/11484] + 18.6 ----- diff --git a/WooCommerce/src/main/AndroidManifest.xml b/WooCommerce/src/main/AndroidManifest.xml index 6e75c9bba77..8969e4cfb5a 100644 --- a/WooCommerce/src/main/AndroidManifest.xml +++ b/WooCommerce/src/main/AndroidManifest.xml @@ -14,10 +14,8 @@ - - + + @@ -49,17 +47,16 @@ + android:windowSoftInputMode="adjustResize" + android:launchMode="singleTop"> - @@ -68,20 +65,19 @@ - + - + - - - - + + + + - - + @@ -98,15 +94,15 @@ - - + + + android:theme="@style/LoginTheme" + android:exported="true"> @@ -159,8 +155,7 @@ - + tools:replace="android:screenOrientation"/> + android:permission="android.permission.BIND_JOB_SERVICE" + android:exported="false" /> - + - - + diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFilterableCardHeader.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFilterableCardHeader.kt deleted file mode 100644 index bb2621d15a8..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardFilterableCardHeader.kt +++ /dev/null @@ -1,129 +0,0 @@ -package com.woocommerce.android.ui.dashboard - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.ContentAlpha -import androidx.compose.material.DropdownMenu -import androidx.compose.material.DropdownMenuItem -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.FilterList -import androidx.compose.runtime.Composable -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.res.dimensionResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.woocommerce.android.R -import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground - -@Composable -fun DashboardFilterableCardHeader( - title: String, - currentFilter: T, - filterList: List, - onFilterSelected: (T) -> Unit, - modifier: Modifier = Modifier, - mapper: @Composable (T) -> String = { it.toString() } -) { - Row( - horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.minor_100)), - verticalAlignment = Alignment.CenterVertically, - modifier = modifier.padding( - start = dimensionResource(id = R.dimen.major_100) - ) - ) { - Text( - text = title, - style = MaterialTheme.typography.body2, - color = MaterialTheme.colors.onSurface - ) - - Text( - text = mapper(currentFilter), - style = MaterialTheme.typography.body2, - color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium) - ) - - Spacer(modifier = Modifier.weight(1f)) - - Box { - var isMenuExpanded by remember { mutableStateOf(false) } - IconButton( - onClick = { isMenuExpanded = true } - ) { - Icon( - imageVector = Icons.Default.FilterList, - contentDescription = stringResource( - id = R.string.dashboard_filter_menu_content_description - ), - tint = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium) - ) - } - - DropdownMenu( - expanded = isMenuExpanded, - onDismissRequest = { isMenuExpanded = false }, - modifier = Modifier - .defaultMinSize(minWidth = 250.dp) - ) { - filterList.forEach { - DropdownMenuItem( - onClick = { - onFilterSelected(it) - isMenuExpanded = false - } - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.minor_100)) - ) { - Text(text = mapper(it)) - Spacer(modifier = Modifier.weight(1f)) - if (currentFilter == it) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = stringResource(id = androidx.compose.ui.R.string.selected), - tint = MaterialTheme.colors.primary - ) - } else { - Spacer(modifier = Modifier.size(dimensionResource(R.dimen.image_minor_50))) - } - } - } - } - } - } - } -} - -@Composable -@Preview -private fun DashboardFilterableCardHeaderPreview() { - val filters = remember { - listOf("Filter 1", "Filter 2", "Filter 3") - } - var currentFilter by remember { mutableStateOf("Filter 1") } - WooThemeWithBackground { - DashboardFilterableCardHeader( - title = "Title", - currentFilter = currentFilter, - filterList = filters, - onFilterSelected = { currentFilter = it }, - modifier = Modifier - ) - } -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt index edf6294c2b0..c515586fbd3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardDataStore.kt @@ -23,7 +23,7 @@ class DashboardDataStore @Inject constructor( SiteComponentEntryPoint::class.java ).dashboardDataStore() - val widgets: Flow> = dataStore.data + val dashboard: Flow = dataStore.data .catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { @@ -40,25 +40,6 @@ class DashboardDataStore @Inject constructor( it } } - .map { - val widgets = it.widgetsList.toMutableList() - - // Add any new widgets that are not present in the saved configuration - if (supportedWidgets.size != widgets.size) { - supportedWidgets.filter { type -> - widgets.none { widget -> widget.type == type.name } - }.forEach { type -> - widgets.add( - DashboardWidgetDataModel.newBuilder() - .setType(type.name) - .setIsAdded(false) - .build() - ) - } - } - - return@map widgets - } suspend fun updateDashboard(dashboard: DashboardDataModel) { runCatching { @@ -68,13 +49,10 @@ class DashboardDataStore @Inject constructor( } } - private fun getDefaultWidgets() = supportedWidgets.map { + private fun getDefaultWidgets() = DashboardWidget.Type.entries.map { DashboardWidgetDataModel.newBuilder() .setType(it.name) .setIsAdded(true) .build() } - - // Use the feature flag [DYNAMIC_DASHBOARD_M2] to filter out unsupported widgets during development - private val supportedWidgets: List = DashboardWidget.Type.entries } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt index a8d70de26fd..4edd4edea0a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/data/DashboardRepository.kt @@ -12,6 +12,7 @@ import dagger.hilt.android.scopes.ActivityRetainedScoped import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import javax.inject.Inject @@ -50,7 +51,7 @@ class DashboardRepository @Inject constructor( ) val widgets = combine( - dashboardDataStore.widgets, + dashboardDataStore.dashboard.map { it.widgetsList }, statsWidgetsStatus, blazeWidgetStatus, onboardingWidgetStatus diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt index d26af1b66ea..9cdf4192c7a 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuFragment.kt @@ -1,6 +1,5 @@ package com.woocommerce.android.ui.moremenu -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -21,7 +20,6 @@ import com.woocommerce.android.ui.main.AppBarStatus import com.woocommerce.android.ui.main.MainActivity import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.NavigateToSettingsEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.NavigateToSubscriptionsEvent -import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.NavigateToWooPosEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.OpenBlazeCampaignCreationEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.OpenBlazeCampaignListEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.StartSitePickerEvent @@ -32,7 +30,6 @@ import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.ViewP import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.ViewReviewsEvent import com.woocommerce.android.ui.moremenu.MoreMenuViewModel.MoreMenuEvent.ViewStoreEvent import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam -import com.woocommerce.android.ui.woopos.root.WooPosActivity import com.woocommerce.android.util.ChromeCustomTabUtils import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -102,15 +99,9 @@ class MoreMenuFragment : TopLevelFragment() { is ViewPayments -> navigateToPayments() is OpenBlazeCampaignCreationEvent -> openBlazeCreationFlow() is OpenBlazeCampaignListEvent -> openBlazeCampaignList() - is NavigateToWooPosEvent -> openWooPos() } } } - - private fun openWooPos() { - startActivity(Intent(requireContext(), WooPosActivity::class.java)) - } - private fun openBlazeCampaignList() { findNavController().navigateSafely( MoreMenuFragmentDirections.actionMoreMenuToBlazeCampaignListFragment() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt index 81b54b2e4ce..3736c1aa4fa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModel.kt @@ -28,7 +28,6 @@ import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus import com.woocommerce.android.ui.payments.taptopay.isAvailable import com.woocommerce.android.ui.plans.domain.SitePlan import com.woocommerce.android.ui.plans.repository.SitePlanRepository -import com.woocommerce.android.ui.woopos.IsWooPosEnabled import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.ResourceProvider import com.woocommerce.android.viewmodel.ScopedViewModel @@ -58,7 +57,6 @@ class MoreMenuViewModel @Inject constructor( private val moreMenuNewFeatureHandler: MoreMenuNewFeatureHandler, private val tapToPayAvailabilityStatus: TapToPayAvailabilityStatus, private val isBlazeEnabled: IsBlazeEnabled, - private val isWooPosEnabled: IsWooPosEnabled, ) : ScopedViewModel(savedState) { val moreMenuViewState = combine( @@ -135,15 +133,6 @@ class MoreMenuViewModel @Inject constructor( icon = R.drawable.ic_more_menu_inbox, isEnabled = moreMenuRepository.isInboxEnabled(), onClick = ::onInboxButtonClick - ), - MenuUiButton( - title = R.string.more_menu_button_woo_pos, - description = R.string.more_menu_button_woo_pos_description, - icon = R.drawable.ic_more_menu_payments, - isEnabled = isWooPosEnabled(), - onClick = { - triggerEvent(MoreMenuEvent.NavigateToWooPosEvent) - } ) ) @@ -327,6 +316,5 @@ class MoreMenuViewModel @Inject constructor( object ViewReviewsEvent : MoreMenuEvent() object ViewInboxEvent : MoreMenuEvent() object ViewCouponsEvent : MoreMenuEvent() - object NavigateToWooPosEvent : MoreMenuEvent() } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt index 521e5372713..cc214cbc0eb 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/list/ProductListToolbarHelper.kt @@ -39,10 +39,6 @@ class ProductListToolbarHelper @Inject constructor( private var scanBarcodeMenuItem: MenuItem? = null private var searchView: SearchView? = null - private val refreshOptionsMenuCallback = Runnable { - refreshOptionsMenu() - } - override fun onCreate(owner: LifecycleOwner) { (activity as FragmentActivity).onBackPressedDispatcher.addCallback( owner, @@ -90,7 +86,7 @@ class ProductListToolbarHelper @Inject constructor( this.viewModel = productListViewModel this.binding = binding - fragment.viewLifecycleOwner.lifecycle.addObserver(this) + fragment.lifecycle.addObserver(this) if (productListViewModel.isSearching()) { binding.productsSearchTabView.isVisible = true @@ -102,7 +98,6 @@ class ProductListToolbarHelper @Inject constructor( override fun onDestroy(owner: LifecycleOwner) { disableSearchListeners() - binding?.toolbar?.removeCallbacks(refreshOptionsMenuCallback) listFragment = null searchMenuItem = null scanBarcodeMenuItem = null @@ -174,7 +169,9 @@ class ProductListToolbarHelper @Inject constructor( // We want to refresh the options menu after the toolbar has been inflated // Otherwise, logic in it will be executed before the toolbar is in restored state after configuration change - toolbar.post(refreshOptionsMenuCallback) + toolbar.post { + refreshOptionsMenu() + } } private fun refreshOptionsMenu() { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/IsWooPosEnabled.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/IsWooPosEnabled.kt deleted file mode 100644 index 9e7ef857473..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/IsWooPosEnabled.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.woocommerce.android.ui.woopos - -import com.woocommerce.android.util.FeatureFlag -import javax.inject.Inject - -class IsWooPosEnabled @Inject constructor() { - operator fun invoke() = FeatureFlag.WOO_POS.isEnabled() -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt deleted file mode 100644 index 274c599943b..00000000000 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/woopos/root/WooPosActivity.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.woocommerce.android.ui.woopos.root - -import android.os.Bundle -import androidx.activity.compose.setContent -import androidx.appcompat.app.AppCompatActivity -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier - -class WooPosActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - Box( - Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Text( - text = "Woo POS!", - style = MaterialTheme.typography.h3, - color = MaterialTheme.colors.primary, - ) - } - } - } -} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt index 8a6db970eff..0384d2817bd 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt @@ -6,7 +6,6 @@ import android.content.Context * "Feature flags" are used to hide in-progress features from release versions */ enum class FeatureFlag { - WOO_POS, DB_DOWNGRADE, MORE_MENU_INBOX, WC_SHIPPING_BANNER, @@ -18,8 +17,7 @@ enum class FeatureFlag { NEW_SHIPPING_SUPPORT, DYNAMIC_DASHBOARD, APP_PASSWORD_TUTORIAL, - EOSL_M1, - DYNAMIC_DASHBOARD_M2; + EOSL_M1; fun isEnabled(context: Context? = null): Boolean { return when (this) { @@ -29,11 +27,9 @@ enum class FeatureFlag { OTHER_PAYMENT_METHODS, MORE_MENU_INBOX, - WOO_POS, WC_SHIPPING_BANNER, BETTER_CUSTOMER_SEARCH_M2, - ORDER_CREATION_AUTO_TAX_RATE, - DYNAMIC_DASHBOARD_M2 -> PackageUtils.isDebugBuild() + ORDER_CREATION_AUTO_TAX_RATE -> PackageUtils.isDebugBuild() DYNAMIC_DASHBOARD, CONNECTIVITY_TOOL, diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 2a0023651b1..9c8e9495bb2 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -371,7 +371,6 @@ Months Years Change date range button - Open filter dropdown Visitors Orders @@ -418,7 +417,6 @@ Unable to load data contact support.]]> - Status @@ -3660,9 +3658,6 @@ Settings Update your preferences - Woo Pos - Temporary entry point to POS mode - diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt index e92b4c183bc..7e0dbb0e4b6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/moremenu/MoreMenuViewModelTests.kt @@ -9,7 +9,6 @@ import com.woocommerce.android.ui.moremenu.domain.MoreMenuRepository import com.woocommerce.android.ui.payments.taptopay.TapToPayAvailabilityStatus import com.woocommerce.android.ui.plans.domain.SitePlan import com.woocommerce.android.ui.plans.repository.SitePlanRepository -import com.woocommerce.android.ui.woopos.IsWooPosEnabled import com.woocommerce.android.util.captureValues import com.woocommerce.android.util.runAndCaptureValues import com.woocommerce.android.viewmodel.BaseUnitTest @@ -73,9 +72,6 @@ class MoreMenuViewModelTests : BaseUnitTest() { private val isBlazeEnabled: IsBlazeEnabled = mock { onBlocking { invoke() } doReturn true } - private val isWooPosEnabled: IsWooPosEnabled = mock { - on { invoke() } doReturn true - } private val blazeCampaignsStore: BlazeCampaignsStore = mock() @@ -96,7 +92,6 @@ class MoreMenuViewModelTests : BaseUnitTest() { blazeCampaignsStore = blazeCampaignsStore, tapToPayAvailabilityStatus = tapToPayAvailabilityStatus, isBlazeEnabled = isBlazeEnabled, - isWooPosEnabled = isWooPosEnabled, ) } @@ -407,35 +402,4 @@ class MoreMenuViewModelTests : BaseUnitTest() { assertThat(event).isEqualTo(MoreMenuViewModel.MoreMenuEvent.OpenBlazeCampaignListEvent) } - - @Test - fun `given isWooPosEnabled returns false, when building state, then WooPOS button is not displayed`() = - testBlocking { - // GIVEN - setup { - whenever(isWooPosEnabled.invoke()).thenReturn(false) - } - - // WHEN - val states = viewModel.moreMenuViewState.captureValues() - - // THEN - assertThat(states.last().generalMenuItems.first { it.title == R.string.more_menu_button_woo_pos }.isEnabled) - .isFalse() - } - - @Test - fun `given isWooPosEnabled returns true, when building state, then WooPOS button is displayed`() = testBlocking { - // GIVEN - setup { - whenever(isWooPosEnabled.invoke()).thenReturn(true) - } - - // WHEN - val states = viewModel.moreMenuViewState.captureValues() - - // THEN - assertThat(states.last().generalMenuItems.first { it.title == R.string.more_menu_button_woo_pos }.isEnabled) - .isTrue() - } } From ea0892ec2c0de187a06b5b4f490d8d81b394584c Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Tue, 14 May 2024 22:46:30 +0200 Subject: [PATCH 14/15] Fix the unit tests --- .../details/ProductDetailViewModelTest.kt | 67 +++---------------- 1 file changed, 11 insertions(+), 56 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index 9bc6fdc2112..ebb1eeff1fb 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt @@ -51,7 +51,6 @@ import org.mockito.kotlin.whenever import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore -import org.wordpress.android.fluxc.store.SiteStore.SiteVisibility import org.wordpress.android.fluxc.store.WCProductStore import org.wordpress.android.fluxc.store.WooCommerceStore import java.math.BigDecimal @@ -78,9 +77,6 @@ class ProductDetailViewModelTest : BaseUnitTest() { private val productTagsRepository: ProductTagsRepository = mock() private val mediaFilesRepository: MediaFilesRepository = mock() private val variationRepository: VariationRepository = mock() - private var selectedSite: SelectedSite = mock { - on { get() } doReturn SiteModel() - } private val resources: ResourceProvider = mock { on(it.getString(any())).thenAnswer { i -> i.arguments[0].toString() } @@ -131,12 +127,16 @@ class ProductDetailViewModelTest : BaseUnitTest() { private lateinit var viewModel: ProductDetailViewModel + private var selectedSite: SelectedSite = mock { + on { get() } doReturn SiteModel().apply { setIsPrivate(false) } + } + private val productWithParameters = ProductDetailViewModel.ProductDetailViewState( productDraft = product, auxiliaryState = ProductDetailViewModel.ProductDetailViewState.AuxiliaryState.None, uploadingImageUris = emptyList(), showBottomSheetButton = true, - areImagesAvailable = false, + areImagesAvailable = true, ) private val expectedCards = listOf( @@ -1067,22 +1067,10 @@ class ProductDetailViewModelTest : BaseUnitTest() { @Test fun `given selected site is private, when product detail is opened, then images are not available`() = testBlocking { // GIVEN - viewModel.start() - - // WHEN - var productData: ProductDetailViewModel.ProductDetailViewState? = null - viewModel.productDetailViewStateData.observeForever { _, new -> productData = new } - - // THEN - Assertions.assertThat(productData?.areImagesAvailable).isFalse() - } - - @Test - fun `given selected site is public, when product detail is opened, then images are available`() = testBlocking { - // GIVEN - selectedSite = mock { - on { get() } doReturn SiteModel().apply { publishedStatus = SiteVisibility.COMING_SOON.value() } + val selectedSite: SelectedSite = mock { + on { get() } doReturn SiteModel().apply { setIsPrivate(true) } } + savedState = ProductDetailFragmentArgs(ProductDetailFragment.Mode.ShowProduct(PRODUCT_REMOTE_ID)) .toSavedStateHandle() viewModel = spy( @@ -1114,52 +1102,19 @@ class ProductDetailViewModelTest : BaseUnitTest() { isWindowClassLargeThanCompact = isWindowClassLargeThanCompact, ) ) + viewModel.start() // WHEN var productData: ProductDetailViewModel.ProductDetailViewState? = null viewModel.productDetailViewStateData.observeForever { _, new -> productData = new } // THEN - Assertions.assertThat(productData?.areImagesAvailable).isTrue() + Assertions.assertThat(productData?.areImagesAvailable).isFalse() } @Test - fun `given selected site is coming soon, when product detail is opened, then images are available`() = testBlocking { + fun `given selected site is public, when product detail is opened, then images are available`() = testBlocking { // GIVEN - selectedSite = mock { - on { get() } doReturn SiteModel().apply { publishedStatus = SiteVisibility.COMING_SOON.value() } - } - savedState = ProductDetailFragmentArgs(ProductDetailFragment.Mode.ShowProduct(PRODUCT_REMOTE_ID)) - .toSavedStateHandle() - viewModel = spy( - ProductDetailViewModel( - savedState = savedState, - dispatchers = coroutinesTestRule.testDispatchers, - parameterRepository = parameterRepository, - productRepository = productRepository, - networkStatus = networkStatus, - currencyFormatter = currencyFormatter, - resources = resources, - productCategoriesRepository = productCategoriesRepository, - productTagsRepository = productTagsRepository, - mediaFilesRepository = mediaFilesRepository, - variationRepository = variationRepository, - mediaFileUploadHandler = mediaFileUploadHandler, - appPrefsWrapper = prefsWrapper, - addonRepository = addonRepository, - generateVariationCandidates = generateVariationCandidates, - duplicateProduct = mock(), - tracker = tracker, - selectedSite = selectedSite, - getProductQuantityRules = mock(), - getBundledProductsCount = mock(), - getComponentProducts = mock(), - productListRepository = mock(), - isBlazeEnabled = isBlazeEnabled, - isProductCurrentlyPromoted = mock(), - isWindowClassLargeThanCompact = isWindowClassLargeThanCompact, - ) - ) viewModel.start() // WHEN From dcd4c0c0a823705600dc34cb624a82a5d6a1381b Mon Sep 17 00:00:00 2001 From: Ondrej Ruttkay Date: Wed, 15 May 2024 11:12:22 +0200 Subject: [PATCH 15/15] Fix the broken unit tests --- .../ProductDetailViewModelGenerateVariationFlowTest.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelGenerateVariationFlowTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelGenerateVariationFlowTest.kt index c8c42139eb8..0d54f7e8393 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelGenerateVariationFlowTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelGenerateVariationFlowTest.kt @@ -10,6 +10,7 @@ import com.woocommerce.android.media.ProductImagesServiceWrapper import com.woocommerce.android.model.RequestResult import com.woocommerce.android.model.VariantOption import com.woocommerce.android.tools.NetworkStatus +import com.woocommerce.android.tools.SelectedSite import com.woocommerce.android.ui.media.MediaFileUploadHandler import com.woocommerce.android.ui.products.ParameterRepository import com.woocommerce.android.ui.products.ProductTestUtils @@ -40,6 +41,7 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.stub import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.WooCommerceStore @ExperimentalCoroutinesApi @@ -78,6 +80,10 @@ class ProductDetailViewModelGenerateVariationFlowTest : BaseUnitTest() { private val product = ProductTestUtils.generateProduct(PRODUCT_REMOTE_ID) private val isWindowClassLargeThanCompact: IsWindowClassLargeThanCompact = mock() + private var selectedSite: SelectedSite = mock { + on { get() } doReturn SiteModel().apply { setIsPrivate(false) } + } + private lateinit var viewModel: ProductDetailViewModel @Before @@ -111,7 +117,7 @@ class ProductDetailViewModelGenerateVariationFlowTest : BaseUnitTest() { generateVariationCandidates = generateVariationCandidates, duplicateProduct = mock(), tracker = tracker, - selectedSite = mock(), + selectedSite = selectedSite, getProductQuantityRules = mock(), getBundledProductsCount = mock(), getComponentProducts = mock(),