Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
Expand Up @@ -114,7 +114,8 @@ extension Storage.GeneralStoreSettings {
lastSelectedStockType: NullableCopiableProp<String> = .copy,
lastSelectedOrderStatus: NullableCopiableProp<String> = .copy,
favoriteProductIDs: CopiableProp<[Int64]> = .copy,
searchTermsByKey: CopiableProp<[String: [String]]> = .copy
searchTermsByKey: CopiableProp<[String: [String]]> = .copy,
isPOSTabVisible: NullableCopiableProp<Bool> = .copy
) -> Storage.GeneralStoreSettings {
let storeID = storeID ?? self.storeID
let isTelemetryAvailable = isTelemetryAvailable ?? self.isTelemetryAvailable
Expand All @@ -135,6 +136,7 @@ extension Storage.GeneralStoreSettings {
let lastSelectedOrderStatus = lastSelectedOrderStatus ?? self.lastSelectedOrderStatus
let favoriteProductIDs = favoriteProductIDs ?? self.favoriteProductIDs
let searchTermsByKey = searchTermsByKey ?? self.searchTermsByKey
let isPOSTabVisible = isPOSTabVisible ?? self.isPOSTabVisible

return Storage.GeneralStoreSettings(
storeID: storeID,
Expand All @@ -155,7 +157,8 @@ extension Storage.GeneralStoreSettings {
lastSelectedStockType: lastSelectedStockType,
lastSelectedOrderStatus: lastSelectedOrderStatus,
favoriteProductIDs: favoriteProductIDs,
searchTermsByKey: searchTermsByKey
searchTermsByKey: searchTermsByKey,
isPOSTabVisible: isPOSTabVisible
)
}
}
13 changes: 11 additions & 2 deletions Storage/Storage/Model/GeneralStoreSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public struct GeneralStoreSettings: Codable, Equatable, GeneratedCopiable {
///
public var searchTermsByKey: [String: [String]]

/// Whether the POS tab is visible for this store.
///
public var isPOSTabVisible: Bool?

public init(storeID: String? = nil,
isTelemetryAvailable: Bool = false,
telemetryLastReportedTime: Date? = nil,
Expand All @@ -100,7 +104,8 @@ public struct GeneralStoreSettings: Codable, Equatable, GeneratedCopiable {
lastSelectedStockType: String? = nil,
lastSelectedOrderStatus: String? = nil,
favoriteProductIDs: [Int64] = [],
searchTermsByKey: [String: [String]] = [:]) {
searchTermsByKey: [String: [String]] = [:],
isPOSTabVisible: Bool? = nil) {
self.storeID = storeID
self.isTelemetryAvailable = isTelemetryAvailable
self.telemetryLastReportedTime = telemetryLastReportedTime
Expand All @@ -120,6 +125,7 @@ public struct GeneralStoreSettings: Codable, Equatable, GeneratedCopiable {
self.lastSelectedOrderStatus = lastSelectedOrderStatus
self.favoriteProductIDs = favoriteProductIDs
self.searchTermsByKey = searchTermsByKey
self.isPOSTabVisible = isPOSTabVisible
}

public func erasingSelectedTaxRateID() -> GeneralStoreSettings {
Expand All @@ -140,7 +146,8 @@ public struct GeneralStoreSettings: Codable, Equatable, GeneratedCopiable {
lastSelectedStockType: lastSelectedStockType,
lastSelectedOrderStatus: lastSelectedOrderStatus,
favoriteProductIDs: favoriteProductIDs,
searchTermsByKey: searchTermsByKey)
searchTermsByKey: searchTermsByKey,
isPOSTabVisible: isPOSTabVisible)
}
}

Expand Down Expand Up @@ -174,6 +181,8 @@ extension GeneralStoreSettings {
forKey: .favoriteProductIDs) ?? []
self.searchTermsByKey = try container.decodeIfPresent([String: [String]].self, forKey: .searchTermsByKey) ?? [:]

self.isPOSTabVisible = try container.decodeIfPresent(Bool.self, forKey: .isPOSTabVisible)

// Decode new properties with `decodeIfPresent` and provide a default value if necessary.
}
}
1 change: 1 addition & 0 deletions WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ private extension POSTabCoordinator {
guard let self else { return }
let collectOrderPaymentAnalyticsTracker = POSCollectOrderPaymentAnalytics()
let cardPresentPaymentService = await CardPresentPaymentService(siteID: siteID,
stores: storesManager,
collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker)
if let receiptService = POSReceiptService(siteID: siteID,
credentials: credentials),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import enum WooFoundation.CountryCode
import enum WooFoundation.CurrencyCode
import protocol Experiments.FeatureFlagService
import struct Yosemite.SiteSetting
import protocol Yosemite.POSEligibilityServiceProtocol
import protocol Yosemite.StoresManager
import class Yosemite.POSEligibilityService
import struct Yosemite.SystemPlugin
import enum Yosemite.SystemStatusAction
import enum Yosemite.FeatureFlagAction
import enum Yosemite.SettingAction
import protocol Yosemite.PluginsServiceProtocol
Expand Down Expand Up @@ -35,6 +36,8 @@ enum POSEligibilityState: Equatable {
}

protocol POSEntryPointEligibilityCheckerProtocol {
/// Checks the initial visibility of the POS tab.
func checkInitialVisibility() -> Bool
/// Determines whether the site is eligible for POS.
func checkEligibility() async -> POSEligibilityState
}
Expand All @@ -45,6 +48,7 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {
private let siteSettings: SelectedSiteSettings
private let currencySettings: CurrencySettings
private let pluginsService: PluginsServiceProtocol
private let eligibilityService: POSEligibilityServiceProtocol
private let stores: StoresManager
private let featureFlagService: FeatureFlagService

Expand All @@ -53,17 +57,24 @@ final class POSTabEligibilityChecker: POSEntryPointEligibilityCheckerProtocol {
siteSettings: SelectedSiteSettings = ServiceLocator.selectedSiteSettings,
currencySettings: CurrencySettings = ServiceLocator.currencySettings,
pluginsService: PluginsServiceProtocol = PluginsService(storageManager: ServiceLocator.storageManager),
eligibilityService: POSEligibilityServiceProtocol = POSEligibilityService(),
stores: StoresManager = ServiceLocator.stores,
featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) {
self.siteID = siteID
self.userInterfaceIdiom = userInterfaceIdiom
self.siteSettings = siteSettings
self.currencySettings = currencySettings
self.pluginsService = pluginsService
self.eligibilityService = eligibilityService
self.stores = stores
self.featureFlagService = featureFlagService
}

/// Checks the initial visibility of the POS tab without dependance on network requests.
func checkInitialVisibility() -> Bool {
eligibilityService.loadCachedPOSTabVisibility(siteID: siteID) ?? false
}

/// Determines whether the POS entry point can be shown based on the selected store and feature gates.
func checkEligibility() async -> POSEligibilityState {
guard #available(iOS 17.0, *) else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ final class HubMenuCoordinator {
///
func activate(siteID: Int64) {
hubMenuController = HubMenuViewController(siteID: siteID,
stores: storesManager,
tapToPayBadgePromotionChecker: tapToPayBadgePromotionChecker)
if let hubMenuController = hubMenuController {
let navigationController = UINavigationController(rootViewController: hubMenuController)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ final class HubMenuViewController: UIHostingController<HubMenu> {
private var shouldShowNavigationBar = false

init(siteID: Int64,
stores: StoresManager = ServiceLocator.stores,
tapToPayBadgePromotionChecker: TapToPayBadgePromotionChecker) {
self.viewModel = HubMenuViewModel(siteID: siteID,
tapToPayBadgePromotionChecker: tapToPayBadgePromotionChecker)
tapToPayBadgePromotionChecker: tapToPayBadgePromotionChecker,
stores: stores)

self.tapToPayBadgePromotionChecker = tapToPayBadgePromotionChecker
super.init(rootView: HubMenu(viewModel: viewModel))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ private extension HubMenuViewModel {
func createCardPresentPaymentService() {
Task {
self.cardPresentPaymentService = await CardPresentPaymentService(siteID: siteID,
stores: stores,
collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker)
}
}
Expand Down
28 changes: 22 additions & 6 deletions WooCommerce/Classes/ViewRelated/MainTabBarController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ final class MainTabBarController: UITabBarController {
private let stores: StoresManager
private let analytics: Analytics
private let posEligibilityCheckerFactory: ((_ siteID: Int64) -> POSEntryPointEligibilityCheckerProtocol)
private let posEligibilityService: POSEligibilityServiceProtocol

private var productImageUploadErrorsSubscription: AnyCancellable?

Expand All @@ -139,7 +140,8 @@ final class MainTabBarController: UITabBarController {
productImageUploader: ProductImageUploaderProtocol = ServiceLocator.productImageUploader,
analytics: Analytics = ServiceLocator.analytics,
stores: StoresManager = ServiceLocator.stores,
posEligibilityCheckerFactory: ((Int64) -> POSEntryPointEligibilityCheckerProtocol)? = nil) {
posEligibilityCheckerFactory: ((Int64) -> POSEntryPointEligibilityCheckerProtocol)? = nil,
posEligibilityService: POSEligibilityServiceProtocol = POSEligibilityService()) {
self.featureFlagService = featureFlagService
self.noticePresenter = noticePresenter
self.productImageUploader = productImageUploader
Expand All @@ -148,6 +150,7 @@ final class MainTabBarController: UITabBarController {
self.posEligibilityCheckerFactory = posEligibilityCheckerFactory ?? { siteID in
POSTabEligibilityChecker(siteID: siteID)
}
self.posEligibilityService = posEligibilityService
super.init(coder: coder)
}

Expand All @@ -160,6 +163,7 @@ final class MainTabBarController: UITabBarController {
self.posEligibilityCheckerFactory = { siteID in
POSTabEligibilityChecker(siteID: siteID)
}
self.posEligibilityService = POSEligibilityService()
super.init(coder: coder)
}

Expand Down Expand Up @@ -650,29 +654,34 @@ extension MainTabBarController: DeepLinkNavigator {
//
private extension MainTabBarController {
func observePOSEligibilityForPOSTabVisibility(siteID: Int64) {
guard featureFlagService.isFeatureFlagEnabled(.pointOfSaleAsATabi1) else {
guard let posEligibilityChecker, featureFlagService.isFeatureFlagEnabled(.pointOfSaleAsATabi1) else {
updateTabViewControllers(isPOSTabVisible: false)
viewModel.loadHubMenuTabBadge()
return
}

// Hides POS tab initially.
updateTabViewControllers(isPOSTabVisible: false)
// Sets POS tab initial visibility based on cached value if available.
let initialVisibility = posEligibilityChecker.checkInitialVisibility()
updateTabViewControllers(isPOSTabVisible: initialVisibility)

// Cancels any existing task.
posEligibilityCheckTask?.cancel()

// Starts observing the POS eligibility state.
posEligibilityCheckTask = Task { @MainActor [weak self] in
guard let self, let posEligibilityChecker else { return }
guard let self, let posEligibilityChecker = self.posEligibilityChecker else { return }
let eligibility = await posEligibilityChecker.checkEligibility()
let isPOSTabVisible = eligibility == .eligible
cachePOSTabVisibility(siteID: siteID, isPOSTabVisible: isPOSTabVisible)
updateTabViewControllers(isPOSTabVisible: isPOSTabVisible)
viewModel.loadHubMenuTabBadge()
}
}

func updateTabViewControllers(isPOSTabVisible: Bool) {
guard isPOSTabVisible != self.isPOSTabVisible || (viewControllers?.count ?? 0) == 0 else {
return
}
var controllers = [UIViewController]()
let tabs = WooTab.visibleTabs(isPOSTabVisible: isPOSTabVisible)
tabs.forEach { tab in
Expand Down Expand Up @@ -736,7 +745,8 @@ private extension MainTabBarController {
posTabCoordinator = POSTabCoordinator(
siteID: siteID,
tabContainerController: posContainerController,
viewControllerToPresent: self
viewControllerToPresent: self,
storesManager: stores
)

// Configure hub menu tab coordinator once per logged in session potentially with multiple sites.
Expand Down Expand Up @@ -923,6 +933,12 @@ private extension MainTabBarController {
}
}

private extension MainTabBarController {
func cachePOSTabVisibility(siteID: Int64, isPOSTabVisible: Bool) {
posEligibilityService.cachePOSTabVisibility(siteID: siteID, isVisible: isPOSTabVisible)
}
}

private extension MainTabBarController {
enum Constants {
// Used to delay a second navigation after the previous one is called,
Expand Down
Loading