Skip to content

[Woo POS] Update conditions for showing POS entry point #11513

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
@@ -0,0 +1,28 @@
{
"request": {
"method": "GET",
"urlPathPattern": "/rest/v1.1/jetpack-blogs/161477129/rest-api/",
"queryParameters": {
"path": {
"equalTo": "/wc/v3/settings/products/&_method=get"
},
"json": {
"equalTo": "true"
},
"locale": {
"matches": "(.*)"
}
}
},
"response": {
"status": 200,
"jsonBody": {
"data": {
}
},
"headers": {
"Content-Type": "application/json",
"Connection": "keep-alive"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"request": {
"method": "GET",
"urlPathPattern": "/rest/v1.1/jetpack-blogs/161477129/rest-api/",
"queryParameters": {
"path": {
"equalTo": "/wc/v3/wc_stripe/account/summary/&_method=get"
},
"json": {
"equalTo": "true"
},
"locale": {
"matches": "(.*)"
}
}
},
"response": {
"status": 200,
"jsonBody": {
"data": {
"country": "US",
"storeCurrencies": {
"default": "USD"
}
}
},
"headers": {
"Content-Type": "application/json",
"Connection": "keep-alive"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class DashboardViewModel @Inject constructor(
dashboardTransactionLauncher: DashboardTransactionLauncher,
shouldShowPrivacyBanner: ShouldShowPrivacyBanner,
dashboardRepository: DashboardRepository,
private val feedbackPrefs: FeedbackPrefs
private val feedbackPrefs: FeedbackPrefs,
) : ScopedViewModel(savedState) {
companion object {
private const val DAYS_TO_REDISPLAY_JP_BENEFITS_BANNER = 5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ import com.woocommerce.android.ui.plans.trial.DetermineTrialStatusBarState
import com.woocommerce.android.ui.prefs.PrivacySettingsRepository
import com.woocommerce.android.ui.prefs.RequestedAnalyticsValue
import com.woocommerce.android.ui.whatsnew.FeatureAnnouncementRepository
import com.woocommerce.android.ui.woopos.IsWooPosEnabled
import com.woocommerce.android.util.BuildConfigWrapper
import com.woocommerce.android.util.WooLog
import com.woocommerce.android.util.WooLog.T
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event
import com.woocommerce.android.viewmodel.ScopedViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
Expand All @@ -57,11 +59,16 @@ class MainActivityViewModel @Inject constructor(
moreMenuNewFeatureHandler: MoreMenuNewFeatureHandler,
unseenReviewsCountHandler: UnseenReviewsCountHandler,
determineTrialStatusBarState: DetermineTrialStatusBarState,
isWooPosEnabled: IsWooPosEnabled,
) : ScopedViewModel(savedState) {
init {
launch {
featureAnnouncementRepository.getFeatureAnnouncements(fromCache = false)
}
launch(IO) {
// cache Woo POS eligibility result as soon as possible so that it doesn't block UI
isWooPosEnabled()
}
}

val startDestination = if (selectedSite.exists()) R.id.dashboard else R.id.nav_graph_site_picker
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
package com.woocommerce.android.ui.woopos

import com.woocommerce.android.util.FeatureFlag
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.payments.GetActivePaymentsPlugin
import com.woocommerce.android.util.IsWindowClassExpandedAndBigger
import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore
import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore.InPersonPaymentsPluginType.WOOCOMMERCE_PAYMENTS
import javax.inject.Inject
import javax.inject.Singleton

class IsWooPosEnabled @Inject constructor() {
operator fun invoke() = FeatureFlag.WOO_POS.isEnabled()
@Singleton
class IsWooPosEnabled @Inject constructor(
private val selectedSite: SelectedSite,
private val ippStore: WCInPersonPaymentsStore,
private val getActivePaymentsPlugin: GetActivePaymentsPlugin,
private val isWindowSizeExpandedAndBigger: IsWindowClassExpandedAndBigger,
private val isWooPosFFEnabled: IsWooPosFFEnabled,
) {
private var cachedResult: Boolean? = null

@Suppress("ReturnCount")
suspend operator fun invoke(): Boolean {
Copy link
Contributor

Choose a reason for hiding this comment

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

Another important thing to notice - we have to check the flag first, otherwise all these checks will be performed even on the production build making it slower for no reason at the moment

Copy link
Contributor

@kidinov kidinov May 15, 2024

Choose a reason for hiding this comment

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

Even after we check FF first in here, we may still need to create a ticket to consider early background check + caching here. Currently, we do network requests, and the whole "more" screen doesn't show anything. Depending on the entry point we probably will need to reconsider the approach

Copy link
Contributor Author

@samiuelson samiuelson May 15, 2024

Choose a reason for hiding this comment

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

Thanks @kidinov, good point! Do you think a naive in-memory cache like this is enough for now? –32a4e39

Copy link
Contributor

Choose a reason for hiding this comment

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

@samiuelson
it's not really needed because the state of the UI is cached in the ViewModel anyway, so it's kinda fetched once anyway, but that first time is looking bad. So you can keep it or revert - doesn't matter, but the most important that you moved isWooPosFFEnabled here so on the release builds we won't slow this down

Copy link
Contributor

Choose a reason for hiding this comment

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

@samiuelson Oh I see. The VM initiated even before the fragment is opened. Then the caching probably makes sense even for debug builds - we just need to keep in mind that the app will have to be killed in case of a site change. I'd add a ticket to come back to this closer to the release - we will need cache invalidation here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, now the menu more screen should load faster even the first time because the IsWooPosEnabled-related requests are now executed in the MainActivityViewModel::init – right after the user launches the app (or logs in) and the result is stored in the memory (IsWooPosEnabled is made a singleton).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool – I created the issue to test cache invalidation need later – #11520

cachedResult?.let { return it }

if (!isWooPosFFEnabled()) return false

val selectedSite = selectedSite.getOrNull() ?: return false
val ippPlugin = getActivePaymentsPlugin() ?: return false
val paymentAccount = ippStore.loadAccount(ippPlugin, selectedSite).model ?: return false
val countryCode = paymentAccount.country

return (
countryCode.lowercase() == "us" &&
ippPlugin == WOOCOMMERCE_PAYMENTS &&
paymentAccount.storeCurrencies.default.lowercase() == "usd" &&
isWindowSizeExpandedAndBigger()
).also { cachedResult = it }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.woocommerce.android.ui.woopos

import com.woocommerce.android.util.FeatureFlag
import javax.inject.Inject

class IsWooPosFFEnabled @Inject constructor() {
operator fun invoke(): Boolean {
return FeatureFlag.WOO_POS.isEnabled()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,7 @@ object UiHelpers {
class IsWindowClassLargeThanCompact @Inject constructor(val context: Context) {
operator fun invoke() = context.windowSizeClass != WindowSizeClass.Compact
}

class IsWindowClassExpandedAndBigger @Inject constructor(val context: Context) {
operator fun invoke() = context.windowSizeClass == WindowSizeClass.ExpandedAndBigger
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you know if small tablets or big phones e.g. 7-8 inch will be covered by that? Do we actually plan to support 7-8inch size devices?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current condition targets an 8" or larger diagonal (assuming a standard 16:10 ratio). I added a ticket to discuss it and adjust final values later - #11522

}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class DashboardViewModelTest : BaseUnitTest() {
selectedSite = selectedSite,
shouldShowPrivacyBanner = shouldShowPrivacyBanner,
dashboardRepository = dashboardRepository,
feedbackPrefs = feedbackPrefs
feedbackPrefs = feedbackPrefs,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,8 @@ class MainActivityViewModelTest : BaseUnitTest() {
unseenReviewsCountHandler = unseenReviewsCountHandler,
determineTrialStatusBarState = mock {
onBlocking { invoke(any()) } doReturn emptyFlow()
}
},
isWooPosEnabled = mock(),
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class MoreMenuViewModelTests : BaseUnitTest() {
onBlocking { invoke() } doReturn true
}
private val isWooPosEnabled: IsWooPosEnabled = mock {
on { invoke() } doReturn true
onBlocking { invoke() } doReturn true
}

private val blazeCampaignsStore: BlazeCampaignsStore = mock()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.woocommerce.android.ui.woopos

import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.payments.GetActivePaymentsPlugin
import com.woocommerce.android.util.IsWindowClassExpandedAndBigger
import com.woocommerce.android.viewmodel.BaseUnitTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.model.payments.inperson.WCPaymentAccountResult
import org.wordpress.android.fluxc.model.payments.inperson.WCPaymentAccountResult.WCPaymentAccountStatus.COMPLETE
import org.wordpress.android.fluxc.model.payments.inperson.WCPaymentAccountResult.WCPaymentAccountStatus.StoreCurrencies
import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult
import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore
import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore.InPersonPaymentsPluginType.STRIPE
import org.wordpress.android.fluxc.store.WCInPersonPaymentsStore.InPersonPaymentsPluginType.WOOCOMMERCE_PAYMENTS
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

@OptIn(ExperimentalCoroutinesApi::class)
class IsWooPosEnabledTest : BaseUnitTest() {
private val selectedSite: SelectedSite = mock()
private val ippStore: WCInPersonPaymentsStore = mock()
private val getActivePaymentsPlugin: GetActivePaymentsPlugin = mock()
private val isWindowSizeExpandedAndBigger: IsWindowClassExpandedAndBigger = mock()
private val isWooPosFFEnabled: IsWooPosFFEnabled = mock()

private lateinit var sut: IsWooPosEnabled

@Before
fun setup() = testBlocking {
whenever(selectedSite.getOrNull()).thenReturn(SiteModel())
whenever(getActivePaymentsPlugin()).thenReturn(WOOCOMMERCE_PAYMENTS)
whenever(isWindowSizeExpandedAndBigger()).thenReturn(true)
whenever(ippStore.loadAccount(any(), any())).thenReturn(buildPaymentAccountResult())
whenever(isWooPosFFEnabled()).thenReturn(true)

sut = IsWooPosEnabled(
selectedSite = selectedSite,
ippStore = ippStore,
getActivePaymentsPlugin = getActivePaymentsPlugin,
isWindowSizeExpandedAndBigger = isWindowSizeExpandedAndBigger,
isWooPosFFEnabled = isWooPosFFEnabled,
)
}

@Test
fun `given store not in the US, then return false`() = testBlocking {
val result = buildPaymentAccountResult(countryCode = "CA")
whenever(ippStore.loadAccount(any(), any())).thenReturn(result)
assertFalse(sut())
}

@Test
fun `given not big enough screen, then return false`() = testBlocking {
whenever(isWindowSizeExpandedAndBigger()).thenReturn(false)
assertFalse(sut())
}

@Test
fun `given currency is not USD, then return false`() = testBlocking {
val result = buildPaymentAccountResult(defaultCurrency = "CAD")
whenever(ippStore.loadAccount(any(), any())).thenReturn(result)
assertFalse(sut())
}

@Test
fun `given ipp plugin is not enabled, then return false`() = testBlocking {
whenever(getActivePaymentsPlugin()).thenReturn(null)
assertFalse(sut())
}

@Test
fun `given ipp plugin is not woo payments, then return false`() = testBlocking {
whenever(getActivePaymentsPlugin()).thenReturn(STRIPE)
assertFalse(sut())
}

@Test
fun `given feature flag disabled, then return false`() = testBlocking {
whenever(isWooPosFFEnabled.invoke()).thenReturn(false)
assertFalse(sut())
}

@Test
fun `given big enough screen, woo payments enabled, USD currency and store in the US, then return true`() = testBlocking {
val result = buildPaymentAccountResult(defaultCurrency = "USD", countryCode = "US", status = COMPLETE)
whenever(ippStore.loadAccount(any(), any())).thenReturn(result)
assertTrue(sut())
}

private fun buildPaymentAccountResult(
status: WCPaymentAccountResult.WCPaymentAccountStatus = COMPLETE,
countryCode: String = "US",
defaultCurrency: String = "USD"
) = WooResult(
WCPaymentAccountResult(
status,
hasPendingRequirements = false,
hasOverdueRequirements = false,
currentDeadline = null,
statementDescriptor = "",
storeCurrencies = StoreCurrencies(defaultCurrency, listOf(defaultCurrency)),
country = countryCode,
isLive = true,
testMode = false,
)
)
}
Loading