Skip to content
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

[Woo POS] Show POS Menu with WooCommerce Upgrade description When WooCommerce Version is Outdated #13807

Draft
wants to merge 13 commits into
base: trunk
Choose a base branch
from
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ class AnalyticsTracker private constructor(

// We have to call in non consistent way to match the iOS naming
const val VALUE_MORE_MENU_POS = "pointOfSale"
const val KEY_POS_NOT_ELIGIBLE_REASON = "not_eligible_reason"
const val VALUE_POS_OUTDATED_WOOCOMMERCE_VERSION = "outdated_woocommerce_version"

const val VALUE_MORE_MENU_PAYMENTS_BADGE_VISIBLE = "badge_visible"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.woocommerce.android.ui.moremenu.MoreMenuEvent.NavigateToSubscriptions
import com.woocommerce.android.ui.moremenu.MoreMenuEvent.NavigateToWooPosEvent
import com.woocommerce.android.ui.moremenu.MoreMenuEvent.OpenBlazeCampaignCreationEvent
import com.woocommerce.android.ui.moremenu.MoreMenuEvent.OpenBlazeCampaignListEvent
import com.woocommerce.android.ui.moremenu.MoreMenuEvent.ShowWooPosWooCoreUpdateRequiredEvent
import com.woocommerce.android.ui.moremenu.MoreMenuEvent.StartSitePickerEvent
import com.woocommerce.android.ui.moremenu.MoreMenuEvent.ViewAdminEvent
import com.woocommerce.android.ui.moremenu.MoreMenuEvent.ViewCouponsEvent
Expand All @@ -42,6 +43,7 @@ import com.woocommerce.android.ui.woopos.root.WooPosActivity
import com.woocommerce.android.util.ChromeCustomTabUtils
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.wordpress.android.util.ToastUtils
import javax.inject.Inject

@AndroidEntryPoint
Expand Down Expand Up @@ -113,6 +115,10 @@ class MoreMenuFragment : TopLevelFragment() {
is OpenBlazeCampaignCreationEvent -> openBlazeCreationFlow()
is OpenBlazeCampaignListEvent -> openBlazeCampaignList()
is NavigateToWooPosEvent -> openWooPos()
is ShowWooPosWooCoreUpdateRequiredEvent -> ToastUtils.showToast(
requireContext(),
R.string.more_menu_button_woo_pos_update_woocommerce_version_description
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ private fun MoreMenuSection(section: MoreMenuItemSection) {
section.items.forEach { item ->
when (item.state) {
MoreMenuItemButton.State.Loading -> MoreMenuLoading()
MoreMenuItemButton.State.Visible -> MoreMenuButton(item)
is MoreMenuItemButton.State.Visible -> MoreMenuButton(item)
MoreMenuItemButton.State.Hidden -> Unit
}
Spacer(modifier = Modifier.height(8.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ sealed class MoreMenuEvent : MultiLiveEvent.Event() {

data object ViewCustomersEvent : MoreMenuEvent()
data object NavigateToWooPosEvent : MoreMenuEvent()
data object ShowWooPosWooCoreUpdateRequiredEvent : MoreMenuEvent()
}

data class MoreMenuItemSection(
Expand All @@ -45,12 +46,17 @@ data class MoreMenuItemButton(
@StringRes val description: Int,
@DrawableRes val icon: Int,
@DrawableRes val extraIcon: Int? = null,
val state: State = State.Visible,
val state: State = State.Visible.Enabled,
val badgeState: BadgeState? = null,
val onClick: () -> Unit = {},
) {
enum class State {
Loading, Visible, Hidden,
sealed class State {
data object Loading : State()
sealed class Visible : State() {
data object Enabled : Visible()
data object WooCoreVersionNotSupported : Visible()
}
data object Hidden : State()
}

enum class Type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_CAMPAIGN_LIST_ENTR
import com.woocommerce.android.analytics.AnalyticsEvent.BLAZE_ENTRY_POINT_DISPLAYED
import com.woocommerce.android.analytics.AnalyticsTracker
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_OPTION
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_POS_NOT_ELIGIBLE_REASON
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_ADMIN_MENU
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_COUPONS
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_CUSTOMERS
Expand All @@ -19,6 +20,7 @@ import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_M
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_REVIEWS
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_UPGRADES
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_MORE_MENU_VIEW_STORE
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_POS_OUTDATED_WOOCOMMERCE_VERSION
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
import com.woocommerce.android.extensions.adminUrlOrDefault
import com.woocommerce.android.notifications.UnseenReviewsCountHandler
Expand All @@ -35,6 +37,7 @@ 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.WooPosIsEnabled
import com.woocommerce.android.util.GetWooCorePluginCachedVersion
import com.woocommerce.android.util.WooLog
import com.woocommerce.android.viewmodel.ResourceProvider
import com.woocommerce.android.viewmodel.ScopedViewModel
Expand Down Expand Up @@ -71,7 +74,8 @@ class MoreMenuViewModel @Inject constructor(
private val isGoogleForWooEnabled: IsGoogleForWooEnabled,
private val hasGoogleAdsCampaigns: HasGoogleAdsCampaigns,
private val isWooPosEnabled: WooPosIsEnabled,
private val analyticsTrackerWrapper: AnalyticsTrackerWrapper
private val analyticsTrackerWrapper: AnalyticsTrackerWrapper,
private val getWooCoreVersion: GetWooCorePluginCachedVersion,
) : ScopedViewModel(savedState) {
private var storeHasGoogleAdsCampaigns = false

Expand Down Expand Up @@ -114,8 +118,8 @@ class MoreMenuViewModel @Inject constructor(
buttonsStates: Map<MoreMenuItemButton.Type, MoreMenuItemButton.State>,
count: Int,
paymentsFeatureWasClicked: Boolean
) = listOf(
generatePOSSection(buttonsStates[MoreMenuItemButton.Type.WooPos]!!),
) = listOfNotNull(
generatePOSMenuButtons(buttonsStates),
generateSettingsMenuButtons(buttonsStates[MoreMenuItemButton.Type.Settings]!!),
generateGeneralSection(
unseenReviewsCount = count,
Expand All @@ -126,6 +130,31 @@ class MoreMenuViewModel @Inject constructor(
)
)

private fun generatePOSMenuButtons(
buttonsStates: Map<MoreMenuItemButton.Type, MoreMenuItemButton.State>
): MoreMenuItemSection? {
return buttonsStates[MoreMenuItemButton.Type.WooPos]?.let { wooPosState ->
when (wooPosState) {
is MoreMenuItemButton.State.Loading, is MoreMenuItemButton.State.Visible.Enabled ->
generateWooPosSection(
wooPosState,
R.string.more_menu_button_woo_pos_description
) {
onWooPosButtonClick()
}

is MoreMenuItemButton.State.Visible.WooCoreVersionNotSupported -> generateWooPosSection(
wooPosState,
R.string.more_menu_button_woo_pos_update_woocommerce_version_description
) {
onWooPOSNotEligibleButtonClick(WooPosNotEligible.OutdatedWooCommerceVersion)
}

else -> null
}
}
}

fun onViewResumed() {
moreMenuNewFeatureHandler.markNewFeatureAsSeen()
launch {
Expand All @@ -134,20 +163,25 @@ class MoreMenuViewModel @Inject constructor(
}
}

private fun generatePOSSection(wooPosState: MoreMenuItemButton.State) =
MoreMenuItemSection(
private fun generateWooPosSection(
wooPosState: MoreMenuItemButton.State,
description: Int,
onPosButtonClick: () -> Unit
): MoreMenuItemSection {
return MoreMenuItemSection(
title = null,
items = listOf(
MoreMenuItemButton(
title = R.string.more_menu_button_woo_pos,
description = R.string.more_menu_button_woo_pos_description,
description = description,
icon = R.drawable.ic_more_menu_pos,
extraIcon = R.drawable.ic_more_menu_pos_extra,
state = wooPosState,
onClick = ::onWooPosButtonClick,
onClick = onPosButtonClick,
)
)
)
}

@Suppress("LongMethod")
private fun generateGeneralSection(
Expand Down Expand Up @@ -400,6 +434,20 @@ class MoreMenuViewModel @Inject constructor(
triggerEvent(MoreMenuEvent.NavigateToWooPosEvent)
}

private fun onWooPOSNotEligibleButtonClick(reason: WooPosNotEligible) {
when (reason) {
WooPosNotEligible.OutdatedWooCommerceVersion -> {
trackMoreMenuOptionSelected(
VALUE_MORE_MENU_POS,
extraOptions = mapOf(
KEY_POS_NOT_ELIGIBLE_REASON to VALUE_POS_OUTDATED_WOOCOMMERCE_VERSION
)
)
triggerEvent(MoreMenuEvent.ShowWooPosWooCoreUpdateRequiredEvent)
}
}
}

private fun onViewAdminButtonClick() {
trackMoreMenuOptionSelected(VALUE_MORE_MENU_ADMIN_MENU)
triggerEvent(MoreMenuEvent.ViewAdminEvent(selectedSite.get().adminUrlOrDefault))
Expand Down Expand Up @@ -460,16 +508,54 @@ class MoreMenuViewModel @Inject constructor(
.onStart { emit("") }

private fun checkFeaturesAvailability(): Flow<Map<MoreMenuItemButton.Type, MoreMenuItemButton.State>> {
val initialState = MoreMenuItemButton.Type.entries.associateWith {
MoreMenuItemButton.State.Loading
}.toMutableMap()
val initialState =
MoreMenuItemButton.Type.entries.associateWith<MoreMenuItemButton.Type, MoreMenuItemButton.State> {
MoreMenuItemButton.State.Loading
}.toMutableMap()

return listOf(
doCheckAvailability(MoreMenuItemButton.Type.Blaze) { isBlazeEnabled() },
doCheckAvailability(MoreMenuItemButton.Type.GoogleForWoo) { isGoogleForWooEnabled() },
doCheckAvailability(MoreMenuItemButton.Type.Inbox) { moreMenuRepository.isInboxEnabled() },
doCheckAvailability(MoreMenuItemButton.Type.Settings) { moreMenuRepository.isUpgradesEnabled() },
doCheckAvailability(MoreMenuItemButton.Type.WooPos) { isWooPosEnabled() }
doCheckAvailability(MoreMenuItemButton.Type.Blaze) {
if (isBlazeEnabled()) {
MoreMenuItemButton.State.Visible.Enabled
} else {
MoreMenuItemButton.State.Hidden
}
},
doCheckAvailability(MoreMenuItemButton.Type.GoogleForWoo) {
if (isGoogleForWooEnabled()) {
MoreMenuItemButton.State.Visible.Enabled
} else {
MoreMenuItemButton.State.Hidden
}
},
doCheckAvailability(MoreMenuItemButton.Type.Inbox) {
if (moreMenuRepository.isInboxEnabled()) {
MoreMenuItemButton.State.Visible.Enabled
} else {
MoreMenuItemButton.State.Hidden
}
},
doCheckAvailability(MoreMenuItemButton.Type.Settings) {
if (moreMenuRepository.isUpgradesEnabled()) {
MoreMenuItemButton.State.Visible.Enabled
} else {
MoreMenuItemButton.State.Hidden
}
},
doCheckAvailability(MoreMenuItemButton.Type.WooPos) {
when (isWooPosEnabled()) {
is WooPosIsEnabled.Reason.Disabled.CountryCurrencyNotSupported,
WooPosIsEnabled.Reason.Disabled.FeatureFlagDisabled,
WooPosIsEnabled.Reason.Disabled.InvalidSelectedSite,
WooPosIsEnabled.Reason.Disabled.InvalidSiteSettings,
WooPosIsEnabled.Reason.Disabled.ScreenSizeNotAllowed -> MoreMenuItemButton.State.Hidden

WooPosIsEnabled.Reason.Disabled.WooCoreVersionNotSupported ->
MoreMenuItemButton.State.Visible.WooCoreVersionNotSupported

WooPosIsEnabled.Reason.Enabled -> MoreMenuItemButton.State.Visible.Enabled
}
}
).merge()
.map { update ->
initialState[update.first] = update.second
Expand All @@ -480,12 +566,16 @@ class MoreMenuViewModel @Inject constructor(

private fun doCheckAvailability(
type: MoreMenuItemButton.Type,
checker: suspend () -> Boolean
checker: suspend () -> MoreMenuItemButton.State
): Flow<Pair<MoreMenuItemButton.Type, MoreMenuItemButton.State>> = flow {
val state = if (checker()) MoreMenuItemButton.State.Visible else MoreMenuItemButton.State.Hidden
val state = checker()
emit(type to state)
}

private val SitePlan.formattedPlanName
get() = generateFormattedPlanName(resourceProvider)

sealed class WooPosNotEligible {
data object OutdatedWooCommerceVersion : WooPosNotEligible()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,31 @@ class WooPosIsEnabled @Inject constructor(
private val isRemoteFeatureFlagEnabled: IsRemoteFeatureFlagEnabled,
) {
@Suppress("ReturnCount")
suspend operator fun invoke(): Boolean = coroutineScope {
val selectedSite = selectedSite.getOrNull() ?: return@coroutineScope false
suspend operator fun invoke(): Reason = coroutineScope {
val selectedSite = selectedSite.getOrNull()
?: return@coroutineScope Reason.Disabled.InvalidSelectedSite

if (!isRemoteFeatureFlagEnabled(WOO_POS)) return@coroutineScope false
if (!isScreenSizeAllowed()) return@coroutineScope false
if (!isWooCoreSupportsOrderAutoDraftsAndExtraPaymentsProps()) return@coroutineScope false
if (!isRemoteFeatureFlagEnabled(WOO_POS)) return@coroutineScope Reason.Disabled.FeatureFlagDisabled
if (!isScreenSizeAllowed()) return@coroutineScope Reason.Disabled.ScreenSizeNotAllowed
if (!isWooCoreSupportsOrderAutoDraftsAndExtraPaymentsProps()) {
return@coroutineScope Reason.Disabled.WooCoreVersionNotSupported
}

val siteSettings = wooCommerceStore.getSiteSettings(selectedSite) ?: return@coroutineScope false
val siteSettings = wooCommerceStore.getSiteSettings(selectedSite)
?: return@coroutineScope Reason.Disabled.InvalidSiteSettings

return@coroutineScope isCountryAndCurrencySupported(
countryCode = siteSettings.countryCode,
currency = siteSettings.currencyCode
)
return@coroutineScope if (isCountryAndCurrencySupported(
countryCode = siteSettings.countryCode,
currency = siteSettings.currencyCode
)
) {
Reason.Enabled
} else {
Reason.Disabled.CountryCurrencyNotSupported(
country = siteSettings.countryCode,
currency = siteSettings.currencyCode
)
}
}

private fun isCountryAndCurrencySupported(countryCode: String, currency: String) =
Expand All @@ -47,4 +59,16 @@ class WooPosIsEnabled @Inject constructor(

val SUPPORTED_COUNTRY_CURRENCY_PAIRS = listOf("us" to "usd", "gb" to "gbp")
}

sealed class Reason {
data object Enabled : Reason()
sealed class Disabled : Reason() {
data object InvalidSelectedSite : Disabled()
data object InvalidSiteSettings : Disabled()
data object FeatureFlagDisabled : Disabled()
data object ScreenSizeNotAllowed : Disabled()
data class CountryCurrencyNotSupported(val country: String, val currency: String) : Disabled()
data object WooCoreVersionNotSupported : Disabled()
}
}
}
1 change: 1 addition & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3832,6 +3832,7 @@

<string name="more_menu_button_woo_pos">Point of Sale Mode</string>
<string name="more_menu_button_woo_pos_description">Accept payments at your physical store</string>
<string name="more_menu_button_woo_pos_update_woocommerce_version_description">Upgrade your site to the latest WooCommerce to use Point of Sale</string>

<string name="more_menu_customers_title">Customers</string>

Expand Down
Loading