diff --git a/WooCommerce/Classes/Model/BetaFeature.swift b/WooCommerce/Classes/Model/BetaFeature.swift index d6df3a24523..319c74d4431 100644 --- a/WooCommerce/Classes/Model/BetaFeature.swift +++ b/WooCommerce/Classes/Model/BetaFeature.swift @@ -57,8 +57,7 @@ extension BetaFeature { case .inAppPurchases: return ServiceLocator.featureFlagService.isFeatureFlagEnabled(.inAppPurchasesDebugMenu) case .pointOfSale: - return ServiceLocator.featureFlagService.isFeatureFlagEnabled(.displayPointOfSaleToggle) && - UIDevice.current.userInterfaceIdiom == .pad + return POSEligibilityChecker().isEligible() default: return true } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSEligibilityChecker.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSEligibilityChecker.swift new file mode 100644 index 00000000000..fefbe6d52d0 --- /dev/null +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSEligibilityChecker.swift @@ -0,0 +1,44 @@ +import Foundation +import UIKit +import class WooFoundation.CurrencySettings +import enum WooFoundation.CountryCode +import protocol Experiments.FeatureFlagService +import struct Yosemite.SiteSetting + +/// Determines whether the POS entry point can be shown based on the selected store and feature gates. +final class POSEligibilityChecker { + private let cardPresentPaymentsOnboarding: CardPresentPaymentsOnboardingUseCaseProtocol + private let siteSettings: [SiteSetting] + private let currencySettings: CurrencySettings + private let featureFlagService: FeatureFlagService + + init(cardPresentPaymentsOnboarding: CardPresentPaymentsOnboardingUseCaseProtocol = CardPresentPaymentsOnboardingUseCase(), + siteSettings: [SiteSetting] = ServiceLocator.selectedSiteSettings.siteSettings, + currencySettings: CurrencySettings = ServiceLocator.currencySettings, + featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) { + self.siteSettings = siteSettings + self.currencySettings = currencySettings + self.cardPresentPaymentsOnboarding = cardPresentPaymentsOnboarding + self.featureFlagService = featureFlagService + } + + /// Returns whether the selected store is eligible for POS. + func isEligible() -> Bool { + // Always checks the main POS feature flag before any other checks. + guard featureFlagService.isFeatureFlagEnabled(.displayPointOfSaleToggle) else { + return false + } + + let isCountryCodeUS = SiteAddress(siteSettings: siteSettings).countryCode == CountryCode.US + let isCurrencyUSD = currencySettings.currencyCode == .USD + + // Tablet device + return UIDevice.current.userInterfaceIdiom == .pad + // Woo Payments plugin enabled and user setup complete + && (cardPresentPaymentsOnboarding.state == .completed(plugin: .wcPayOnly) || cardPresentPaymentsOnboarding.state == .completed(plugin: .wcPayPreferred)) + // USD currency + && isCurrencyUSD + // US store location + && isCountryCodeUS + } +} diff --git a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift index 361e2d7b392..0ac7f3cb5f0 100644 --- a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift +++ b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift @@ -82,51 +82,24 @@ private extension HubMenu { .disabled(!viewModel.switchStoreEnabled) } + // Point of Sale + if let menu = viewModel.posElement { + Section { + menuItemView(menu: menu) + } + } + // Settings Section Section(Localization.settings) { ForEach(viewModel.settingsElements, id: \.id) { menu in - Button { - handleTap(menu: menu) - } label: { - Row(title: menu.title, - titleBadge: nil, - iconBadge: menu.iconBadge, - description: menu.description, - icon: .local(menu.icon), - chevron: .leading) - .foregroundColor(Color(menu.iconColor)) - } - .accessibilityIdentifier(menu.accessibilityIdentifier) - .overlay { - NavigationLink(value: menu.id) { - EmptyView() - } - .opacity(0) - } + menuItemView(menu: menu) } } // General Section Section(Localization.general) { ForEach(viewModel.generalElements, id: \.id) { menu in - Button { - handleTap(menu: menu) - } label: { - Row(title: menu.title, - titleBadge: nil, - iconBadge: menu.iconBadge, - description: menu.description, - icon: .local(menu.icon), - chevron: .leading) - .foregroundColor(Color(menu.iconColor)) - } - .accessibilityIdentifier(menu.accessibilityIdentifier) - .overlay { - NavigationLink(value: menu.id) { - EmptyView() - } - .opacity(0) - } + menuItemView(menu: menu) } } } @@ -135,6 +108,28 @@ private extension HubMenu { .accentColor(Color(.listSelectedBackground)) } + @ViewBuilder + func menuItemView(menu: HubMenuItem) -> some View { + Button { + handleTap(menu: menu) + } label: { + Row(title: menu.title, + titleBadge: nil, + iconBadge: menu.iconBadge, + description: menu.description, + icon: .local(menu.icon), + chevron: .leading) + .foregroundColor(Color(menu.iconColor)) + } + .accessibilityIdentifier(menu.accessibilityIdentifier) + .overlay { + NavigationLink(value: menu.id) { + EmptyView() + } + .opacity(0) + } + } + @ViewBuilder func detailView(menuID: String) -> some View { Group { diff --git a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift index 628540699d4..1f22eb943b6 100644 --- a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift @@ -41,6 +41,10 @@ final class HubMenuViewModel: ObservableObject { @Published private(set) var woocommerceAdminURL = WooConstants.URLs.blog.asURL() + /// POS Section Element + /// + @Published private(set) var posElement: HubMenuItem? + /// Settings Elements /// @Published private(set) var settingsElements: [HubMenuItem] = [] @@ -122,6 +126,7 @@ final class HubMenuViewModel: ObservableObject { /// Resets the menu elements displayed on the menu. /// func setupMenuElements() { + setupPOSElement() setupSettingsElements() setupGeneralElements() } @@ -132,6 +137,19 @@ final class HubMenuViewModel: ObservableObject { navigationPath.append(HubMenuNavigationDestination.payments) } + private func setupPOSElement() { + let isBetaFeatureEnabled = generalAppSettings.betaFeatureEnabled(.pointOfSale) + let eligibilityChecker = POSEligibilityChecker(cardPresentPaymentsOnboarding: CardPresentPaymentsOnboardingUseCase(), + siteSettings: ServiceLocator.selectedSiteSettings.siteSettings, + currencySettings: ServiceLocator.currencySettings, + featureFlagService: featureFlagService) + if isBetaFeatureEnabled && eligibilityChecker.isEligible() { + posElement = PointOfSaleEntryPoint() + } else { + posElement = nil + } + } + private func setupSettingsElements() { settingsElements = [Settings()] @@ -153,9 +171,6 @@ final class HubMenuViewModel: ObservableObject { if generalAppSettings.betaFeatureEnabled(.inAppPurchases) { generalElements.append(InAppPurchases()) } - if generalAppSettings.betaFeatureEnabled(.pointOfSale) { - generalElements.append(PointOfSaleEntryPoint()) - } let inboxUseCase = InboxEligibilityUseCase(stores: stores, featureFlagService: featureFlagService) inboxUseCase.isEligibleForInbox(siteID: siteID) { [weak self] isInboxMenuShown in diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 3c4545ca86e..73c6a412b74 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -560,6 +560,7 @@ 02E4908929AE49B9005942AE /* TopPerformersEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E4908829AE49B9005942AE /* TopPerformersEmptyView.swift */; }; 02E4908D29AF216E005942AE /* TopPerformersPeriodView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E4908C29AF216E005942AE /* TopPerformersPeriodView.swift */; }; 02E493EF245C1087000AEA9E /* ProductFormBottomSheetListSelectorCommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E493EE245C1087000AEA9E /* ProductFormBottomSheetListSelectorCommandTests.swift */; }; + 02E4A0832BFB1C4F006D4F87 /* POSEligibilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E4A0822BFB1C4F006D4F87 /* POSEligibilityChecker.swift */; }; 02E4AF7126FC4F16002AD9F4 /* ProductReviewFromNoteParcelFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E4AF7026FC4F16002AD9F4 /* ProductReviewFromNoteParcelFactory.swift */; }; 02E4FD7E2306A8180049610C /* StatsTimeRangeBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E4FD7D2306A8180049610C /* StatsTimeRangeBarViewModel.swift */; }; 02E4FD812306AA890049610C /* StatsTimeRangeBarViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E4FD802306AA890049610C /* StatsTimeRangeBarViewModelTests.swift */; }; @@ -3333,6 +3334,7 @@ 02E4908829AE49B9005942AE /* TopPerformersEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopPerformersEmptyView.swift; sourceTree = ""; }; 02E4908C29AF216E005942AE /* TopPerformersPeriodView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopPerformersPeriodView.swift; sourceTree = ""; }; 02E493EE245C1087000AEA9E /* ProductFormBottomSheetListSelectorCommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormBottomSheetListSelectorCommandTests.swift; sourceTree = ""; }; + 02E4A0822BFB1C4F006D4F87 /* POSEligibilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSEligibilityChecker.swift; sourceTree = ""; }; 02E4AF7026FC4F16002AD9F4 /* ProductReviewFromNoteParcelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductReviewFromNoteParcelFactory.swift; sourceTree = ""; }; 02E4FD7D2306A8180049610C /* StatsTimeRangeBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTimeRangeBarViewModel.swift; sourceTree = ""; }; 02E4FD802306AA890049610C /* StatsTimeRangeBarViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsTimeRangeBarViewModelTests.swift; sourceTree = ""; }; @@ -6898,6 +6900,14 @@ path = Authentication; sourceTree = ""; }; + 02E4A0842BFB1D1F006D4F87 /* POS */ = { + isa = PBXGroup; + children = ( + 02E4A0822BFB1C4F006D4F87 /* POSEligibilityChecker.swift */, + ); + path = POS; + sourceTree = ""; + }; 02E4FD7F2306AA770049610C /* Dashboard */ = { isa = PBXGroup; children = ( @@ -10974,6 +10984,7 @@ E138D4F2269ED99A006EA5C6 /* In-Person Payments */, CE27257A219249B5002B22EB /* Help */, CE22E3F821714639005A6BEF /* Privacy */, + 02E4A0842BFB1D1F006D4F87 /* POS */, 03191AE828E20C9200670723 /* PluginDetailsRowView.swift */, ); path = Settings; @@ -14683,6 +14694,7 @@ B57C744E20F56E3800EEFC87 /* UITableViewCell+Helpers.swift in Sources */, 0295355B245ADF8100BDC42B /* FilterType+Products.swift in Sources */, 02CA63DA23D1ADD100BBF148 /* CameraCaptureCoordinator.swift in Sources */, + 02E4A0832BFB1C4F006D4F87 /* POSEligibilityChecker.swift in Sources */, DE8BEB962ABC19B100F5E56C /* ProductDetailPreviewView.swift in Sources */, 260C31602524ECA900157BC2 /* IssueRefundViewController.swift in Sources */, 4521397027FF53E400964ED3 /* CouponExpiryDateView.swift in Sources */,