Skip to content

Commit 077d33f

Browse files
committed
Show bookings tab when site is eligible
1 parent baa8837 commit 077d33f

File tree

4 files changed

+138
-29
lines changed

4 files changed

+138
-29
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import SwiftUI
2+
3+
/// Hosting view for `BookingsTabView`
4+
///
5+
final class BookingsTabViewHostingController: UIHostingController<BookingsTabView> {
6+
init(siteID: Int64) {
7+
super.init(rootView: BookingsTabView())
8+
configureTabBarItem()
9+
}
10+
11+
@MainActor @preconcurrency required dynamic init?(coder aDecoder: NSCoder) {
12+
fatalError("init(coder:) has not been implemented")
13+
}
14+
15+
override var shouldShowOfflineBanner: Bool {
16+
return true
17+
}
18+
}
19+
20+
private extension BookingsTabViewHostingController {
21+
func configureTabBarItem() {
22+
tabBarItem.image = UIImage(systemName: "calendar")
23+
tabBarItem.title = "Bookings"
24+
tabBarItem.accessibilityIdentifier = "tab-bar-bookings-item"
25+
}
26+
}
27+
28+
/// Main content of the Bookings tab
29+
///
30+
struct BookingsTabView: View {
31+
var body: some View {
32+
NavigationSplitView {
33+
Text("Booking List")
34+
} detail: {
35+
Text("Booking Detail Screen")
36+
}
37+
38+
}
39+
}

WooCommerce/Classes/ViewRelated/MainTabBarController.swift

Lines changed: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
667696
private 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

9881058
private extension MainTabBarController {

WooCommerce/Classes/ViewRelated/TabBar/WooTab+Tag.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ extension WooTab {
1010
return 1
1111
case .products:
1212
return 2
13-
case .pointOfSale:
13+
case .bookings:
1414
return 3
15-
case .hubMenu:
15+
case .pointOfSale:
1616
return 4
17+
case .hubMenu:
18+
return 5
1719
}
1820
}
1921
}

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,7 +1646,6 @@
16461646
68A38DF52B293B030090C263 /* MockProductListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A38DF42B293B030090C263 /* MockProductListViewModel.swift */; };
16471647
68A5221B2BA1804900A6A584 /* PluginDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A5221A2BA1804900A6A584 /* PluginDetailsViewModelTests.swift */; };
16481648
68A905012ACCFC13004C71D3 /* CollapsibleProductCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A905002ACCFC13004C71D3 /* CollapsibleProductCard.swift */; };
1649-
68AC9D292ACE598B0042F784 /* ProductImageThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */; };
16501649
68B3BA262D9147480000B2F2 /* AISettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B3BA252D9147440000B2F2 /* AISettingsView.swift */; };
16511650
68B681162D9257810098D5CD /* PointOfSaleCouponsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */; };
16521651
68B6F22B2ADE7ED500D171FC /* TooltipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B6F22A2ADE7ED500D171FC /* TooltipView.swift */; };
@@ -4846,7 +4845,6 @@
48464845
68A38DF42B293B030090C263 /* MockProductListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProductListViewModel.swift; sourceTree = "<group>"; };
48474846
68A5221A2BA1804900A6A584 /* PluginDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDetailsViewModelTests.swift; sourceTree = "<group>"; };
48484847
68A905002ACCFC13004C71D3 /* CollapsibleProductCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleProductCard.swift; sourceTree = "<group>"; };
4849-
68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductImageThumbnail.swift; sourceTree = "<group>"; };
48504848
68B3BA252D9147440000B2F2 /* AISettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AISettingsView.swift; sourceTree = "<group>"; };
48514849
68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCouponsController.swift; sourceTree = "<group>"; };
48524850
68B6F22A2ADE7ED500D171FC /* TooltipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipView.swift; sourceTree = "<group>"; };

0 commit comments

Comments
 (0)