@@ -23,6 +23,10 @@ enum WooTab {
2323 ///
2424 case products
2525
26+ /// Bookings Tab
27+ ///
28+ case bookings
29+
2630 /// Point of Sale Tab
2731 ///
2832 case pointOfSale
@@ -38,15 +42,18 @@ extension WooTab {
3842 /// - Parameters:
3943 /// - visibleIndex: the index of visible tabs on the tab bar
4044 /// - isPOSTabVisible: indicates if the Point of Sale tab is visible.
41- init ( visibleIndex: Int , isPOSTabVisible: Bool ) {
42- let tabs = WooTab . visibleTabs ( isPOSTabVisible: isPOSTabVisible)
45+ /// - isBookingsTabVisible: indicates if the Bookings tab is visible.
46+ init ( visibleIndex: Int , isPOSTabVisible: Bool , isBookingsTabVisible: Bool = false ) {
47+ let tabs = WooTab . visibleTabs ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible: isBookingsTabVisible)
4348 self = tabs [ visibleIndex]
4449 }
4550
4651 /// Returns the visible tab index.
47- /// - Parameter isPOSTabVisible: indicates if the Point of Sale tab is visible.
48- func visibleIndex( isPOSTabVisible: Bool ) -> Int {
49- let tabs = WooTab . visibleTabs ( isPOSTabVisible: isPOSTabVisible)
52+ /// - Parameters:
53+ /// - isPOSTabVisible: indicates if the Point of Sale tab is visible.
54+ /// - isBookingsTabVisible: indicates if the Bookings tab is visible.
55+ func visibleIndex( isPOSTabVisible: Bool , isBookingsTabVisible: Bool = false ) -> Int {
56+ let tabs = WooTab . visibleTabs ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible: isBookingsTabVisible)
5057 guard let tabIndex = tabs. firstIndex ( where: { $0 == self } ) else {
5158 assertionFailure ( " Trying to get the visible tab index for tab \( self ) while the visible tabs are: \( tabs) " )
5259 return 0
@@ -56,14 +63,23 @@ extension WooTab {
5663
5764 /// Note: currently only the Dashboard tab (My Store) view controller is set up in Main.storyboard.
5865 ///
59- /// - Parameter isPOSTabVisible: indicates if the Point of Sale tab is visible.
66+ /// - Parameters:
67+ /// - isPOSTabVisible: indicates if the Point of Sale tab is visible.
68+ /// - isBookingsTabVisible: indicates if the Bookings tab is visible.
6069 /// - Returns: visible tabs in the tab bar.
61- static func visibleTabs( isPOSTabVisible: Bool ) -> [ WooTab ] {
70+ static func visibleTabs( isPOSTabVisible: Bool , isBookingsTabVisible: Bool = false ) -> [ WooTab ] {
71+ var tabs : [ WooTab ] = [ . myStore, . orders, . products]
72+
73+ if isBookingsTabVisible {
74+ tabs. append ( . bookings)
75+ }
76+
6277 if isPOSTabVisible {
63- return [ . myStore, . orders, . products, . pointOfSale, . hubMenu]
64- } else {
65- return [ . myStore, . orders, . products, . hubMenu]
78+ tabs. append ( . pointOfSale)
6679 }
80+
81+ tabs. append ( . hubMenu)
82+ return tabs
6783 }
6884}
6985
@@ -114,6 +130,8 @@ final class MainTabBarController: UITabBarController {
114130 private let posContainerController = TabContainerController ( )
115131 private var posTabCoordinator : POSTabCoordinator ?
116132
133+ private let bookingsContainerController = TabContainerController ( )
134+
117135 private let hubMenuContainerController = TabContainerController ( )
118136 private var hubMenuTabCoordinator : HubMenuCoordinator ?
119137
@@ -132,7 +150,11 @@ final class MainTabBarController: UITabBarController {
132150 private var posEligibilityCheckTask : Task < Void , Never > ?
133151 private var posCatalogSyncCoordinator : POSCatalogSyncCoordinatorProtocol ?
134152
153+ private var bookingsEligibilityChecker : BookingsTabEligibilityCheckerProtocol ?
154+ private var bookingsEligibilityCheckTask : Task < Void , Never > ?
155+
135156 private var isPOSTabVisible : Bool = false
157+ private var isBookingsTabVisible : Bool = false
136158
137159 private lazy var isProductsSplitViewFeatureFlagOn = featureFlagService. isFeatureFlagEnabled ( . splitViewInProductsTab)
138160
@@ -182,6 +204,7 @@ final class MainTabBarController: UITabBarController {
182204 deinit {
183205 cancellableSiteID? . cancel ( )
184206 posEligibilityCheckTask? . cancel ( )
207+ bookingsEligibilityCheckTask? . cancel ( )
185208 }
186209
187210 // MARK: - Overridden Methods
@@ -192,8 +215,8 @@ final class MainTabBarController: UITabBarController {
192215
193216 delegate = self
194217
195- // POS tab is hidden by default.
196- updateTabViewControllers ( isPOSTabVisible: false )
218+ // POS and Bookings tabs are hidden by default.
219+ updateTabViewControllers ( isPOSTabVisible: false , isBookingsTabVisible : false )
197220 observeSiteIDForViewControllers ( )
198221 observeProductImageUploadStatusUpdates ( )
199222
@@ -220,11 +243,11 @@ final class MainTabBarController: UITabBarController {
220243 }
221244
222245 override func tabBar( _ tabBar: UITabBar , didSelect item: UITabBarItem ) {
223- let currentlySelectedTab = WooTab ( visibleIndex: selectedIndex, isPOSTabVisible: isPOSTabVisible)
246+ let currentlySelectedTab = WooTab ( visibleIndex: selectedIndex, isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
224247 guard let userSelectedIndex = tabBar. items? . firstIndex ( of: item) else {
225248 return
226249 }
227- let userSelectedTab = WooTab ( visibleIndex: userSelectedIndex, isPOSTabVisible: isPOSTabVisible)
250+ let userSelectedTab = WooTab ( visibleIndex: userSelectedIndex, isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
228251
229252 // Did we reselect the already-selected tab?
230253 if currentlySelectedTab == userSelectedTab {
@@ -254,7 +277,7 @@ final class MainTabBarController: UITabBarController {
254277 func navigateToTabWithViewController( _ tab: WooTab , animated: Bool = false , completion: ( ( UIViewController ) -> Void ) ? = nil ) {
255278 dismiss ( animated: animated) { [ weak self] in
256279 guard let self else { return }
257- selectedIndex = tab. visibleIndex ( isPOSTabVisible: isPOSTabVisible)
280+ selectedIndex = tab. visibleIndex ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
258281 guard let selectedViewController else {
259282 return
260283 }
@@ -361,6 +384,9 @@ private extension MainTabBarController {
361384 case . products:
362385 ServiceLocator . analytics. track (
363386 event: . Products. productListSelected ( horizontalSizeClass: UITraitCollection . current. horizontalSizeClass) )
387+ case . bookings:
388+ // TODO: Add bookings tab selected analytics
389+ break
364390 case . hubMenu:
365391 ServiceLocator . analytics. track ( . hubMenuTabSelected)
366392 case . pointOfSale:
@@ -381,6 +407,9 @@ private extension MainTabBarController {
381407 case . products:
382408 ServiceLocator . analytics. track (
383409 event: . Products. productListReselected ( horizontalSizeClass: UITraitCollection . current. horizontalSizeClass) )
410+ case . bookings:
411+ // TODO: Add bookings tab reselected analytics
412+ break
384413 case . hubMenu:
385414 ServiceLocator . analytics. track ( . hubMenuTabReselected)
386415 break
@@ -667,14 +696,14 @@ extension MainTabBarController: DeepLinkNavigator {
667696private extension MainTabBarController {
668697 func observePOSEligibilityForPOSTabVisibility( siteID: Int64 ) {
669698 guard let posEligibilityChecker else {
670- updateTabViewControllers ( isPOSTabVisible: false )
699+ updateTabViewControllers ( isPOSTabVisible: false , isBookingsTabVisible : isBookingsTabVisible )
671700 viewModel. loadHubMenuTabBadge ( )
672701 return
673702 }
674703
675704 // Sets POS tab initial visibility based on cached value if available.
676705 let initialVisibility = posEligibilityChecker. checkInitialVisibility ( )
677- updateTabViewControllers ( isPOSTabVisible: initialVisibility)
706+ updateTabViewControllers ( isPOSTabVisible: initialVisibility, isBookingsTabVisible : isBookingsTabVisible )
678707
679708 // Cancels any existing task.
680709 posEligibilityCheckTask? . cancel ( )
@@ -685,7 +714,7 @@ private extension MainTabBarController {
685714 let isPOSTabVisible = await posEligibilityChecker. checkVisibility ( )
686715 analytics. track ( . pointOfSaleTabVisibilityChecked, withProperties: [ " is_visible " : isPOSTabVisible] )
687716 cachePOSTabVisibility ( siteID: siteID, isPOSTabVisible: isPOSTabVisible)
688- updateTabViewControllers ( isPOSTabVisible: isPOSTabVisible)
717+ updateTabViewControllers ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
689718 viewModel. loadHubMenuTabBadge ( )
690719
691720 // Trigger POS catalog sync if tab is visible and feature flag is enabled
@@ -695,19 +724,20 @@ private extension MainTabBarController {
695724 }
696725 }
697726
698- func updateTabViewControllers( isPOSTabVisible: Bool ) {
699- guard isPOSTabVisible != self . isPOSTabVisible || ( viewControllers? . count ?? 0 ) == 0 else {
727+ func updateTabViewControllers( isPOSTabVisible: Bool , isBookingsTabVisible : Bool = false ) {
728+ guard isPOSTabVisible != self . isPOSTabVisible || isBookingsTabVisible != self . isBookingsTabVisible || ( viewControllers? . count ?? 0 ) == 0 else {
700729 return
701730 }
702731 var controllers = [ UIViewController] ( )
703- let tabs = WooTab . visibleTabs ( isPOSTabVisible: isPOSTabVisible)
732+ let tabs = WooTab . visibleTabs ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
704733 tabs. forEach { tab in
705- let tabIndex = tab. visibleIndex ( isPOSTabVisible: isPOSTabVisible)
734+ let tabIndex = tab. visibleIndex ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
706735 let tabViewController = rootTabViewController ( tab: tab)
707736 controllers. insert ( tabViewController, at: tabIndex)
708737 }
709738 viewControllers = controllers
710739 self . isPOSTabVisible = isPOSTabVisible
740+ self . isBookingsTabVisible = isBookingsTabVisible
711741 }
712742
713743 func rootTabViewController( tab: WooTab ) -> UIViewController {
@@ -718,6 +748,8 @@ private extension MainTabBarController {
718748 return ordersContainerController
719749 case . products:
720750 return isProductsSplitViewFeatureFlagOn ? productsContainerController: productsNavigationController
751+ case . bookings:
752+ return bookingsContainerController
721753 case . hubMenu:
722754 return hubMenuContainerController
723755 case . pointOfSale:
@@ -757,6 +789,12 @@ private extension MainTabBarController {
757789 navigateToContent: { _ in } ) ]
758790 }
759791
792+ // Configures Booking tab.
793+ let bookingsEligibilityChecker = BookingsTabEligibilityChecker ( site: site)
794+ self . bookingsEligibilityChecker = bookingsEligibilityChecker
795+ let bookingsViewController = createBookingsViewController ( siteID: site. siteID)
796+ bookingsContainerController. wrappedController = bookingsViewController
797+
760798 // Configures POS tab coordinator once per logged in site session.
761799 let posEligibilityChecker = posEligibilityCheckerFactory ( site)
762800 self . posEligibilityChecker = posEligibilityChecker
@@ -780,9 +818,10 @@ private extension MainTabBarController {
780818 hubMenuTabCoordinator? . activate ( siteID: siteID)
781819
782820 // Set dashboard to be the default tab.
783- selectedIndex = WooTab . myStore. visibleIndex ( isPOSTabVisible: isPOSTabVisible)
821+ selectedIndex = WooTab . myStore. visibleIndex ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
784822
785823 observePOSEligibilityForPOSTabVisibility ( siteID: siteID)
824+ observeBookingsEligibilityForBookingsTabVisibility ( site: site)
786825 }
787826
788827 func createDashboardViewController( siteID: Int64 ) -> UIViewController {
@@ -816,6 +855,10 @@ private extension MainTabBarController {
816855 }
817856 }
818857
858+ func createBookingsViewController( siteID: Int64 ) -> UIViewController {
859+ BookingsTabViewHostingController ( siteID: siteID)
860+ }
861+
819862 func createHubMenuTabCoordinator( ) -> HubMenuCoordinator {
820863 HubMenuCoordinator ( tabContainerController: hubMenuContainerController,
821864 storesManager: stores,
@@ -828,6 +871,29 @@ private extension MainTabBarController {
828871 }
829872 } )
830873 }
874+
875+ func observeBookingsEligibilityForBookingsTabVisibility( site: Site ) {
876+ guard let bookingsEligibilityChecker else {
877+ updateTabViewControllers ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible: false )
878+ return
879+ }
880+
881+ // Sets Bookings tab initial visibility based on cached value if available.
882+ let initialVisibility = bookingsEligibilityChecker. checkInitialVisibility ( )
883+ updateTabViewControllers ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible: initialVisibility)
884+
885+ // Cancels any existing task.
886+ bookingsEligibilityCheckTask? . cancel ( )
887+
888+ // Starts observing the Bookings eligibility state.
889+ bookingsEligibilityCheckTask = Task { @MainActor [ weak self] in
890+ guard let self, let bookingsEligibilityChecker = self . bookingsEligibilityChecker else { return }
891+ let isBookingsTabVisible = await bookingsEligibilityChecker. checkVisibility ( )
892+ // TODO: Add analytics tracking for bookings tab visibility
893+ cacheBookingsTabVisibility ( siteID: site. siteID, isBookingsTabVisible: isBookingsTabVisible)
894+ updateTabViewControllers ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible: isBookingsTabVisible)
895+ }
896+ }
831897}
832898
833899// MARK: - Hub Menu Tab Badge Updates
@@ -848,7 +914,7 @@ private extension MainTabBarController {
848914
849915 func updateMenuTabBadge( with action: NotificationBadgeActionType ) {
850916 let tab = WooTab . hubMenu
851- let tabIndex = tab. visibleIndex ( isPOSTabVisible: isPOSTabVisible)
917+ let tabIndex = tab. visibleIndex ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
852918 let input = NotificationsBadgeInput ( action: action, tab: tab, tabBar: self . tabBar, tabIndex: tabIndex)
853919
854920 self . notificationsBadge. updateBadge ( with: input)
@@ -865,7 +931,7 @@ private extension MainTabBarController {
865931 }
866932
867933 let tab = WooTab . orders
868- let tabIndex = tab. visibleIndex ( isPOSTabVisible: isPOSTabVisible)
934+ let tabIndex = tab. visibleIndex ( isPOSTabVisible: isPOSTabVisible, isBookingsTabVisible : isBookingsTabVisible )
869935
870936 guard let orderTab: UITabBarItem = self . tabBar. items ? [ tabIndex] else {
871937 return
@@ -983,6 +1049,10 @@ private extension MainTabBarController {
9831049 func cachePOSTabVisibility( siteID: Int64 , isPOSTabVisible: Bool ) {
9841050 posEligibilityService. cachePOSTabVisibility ( siteID: siteID, isVisible: isPOSTabVisible)
9851051 }
1052+
1053+ func cacheBookingsTabVisibility( siteID: Int64 , isBookingsTabVisible: Bool ) {
1054+ UserDefaults . standard. cacheBookingsTabVisibility ( siteID: siteID, isVisible: isBookingsTabVisible)
1055+ }
9861056}
9871057
9881058private extension MainTabBarController {
0 commit comments