Skip to content

Commit ed7c78b

Browse files
authored
Merge branch 'trunk' into woomob-949-woo-mobile-pos-orders-display-badge-on-most-recent-orders
2 parents 667bdf5 + 2f47cf8 commit ed7c78b

File tree

5 files changed

+150
-4
lines changed

5 files changed

+150
-4
lines changed

WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ struct PointOfSaleEntryPointView: View {
77
@State private var posModel: PointOfSaleAggregateModel?
88
@StateObject private var posModalManager = POSModalManager()
99
@StateObject private var posSheetManager = POSSheetManager()
10+
@StateObject private var posCoverManager = POSFullScreenCoverManager()
1011
@State private var posEntryPointController: POSEntryPointController
1112
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
1213

@@ -77,6 +78,7 @@ struct PointOfSaleEntryPointView: View {
7778
}
7879
.environmentObject(posModalManager)
7980
.environmentObject(posSheetManager)
81+
.environmentObject(posCoverManager)
8082
.injectKeyboardObserver()
8183
.onAppear {
8284
onPointOfSaleModeActiveStateChange(true)
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import SwiftUI
2+
3+
extension View {
4+
/// Shows a full screen cover that automatically manages modal hierarchy.
5+
/// This is a wrapper around SwiftUI's fullScreenCover that integrates with POS modal system.
6+
///
7+
/// Full screen covers automatically establish a new presentation layer, so:
8+
/// - Modals/sheets from views behind the cover will not show
9+
/// - Modals/sheet within the cover content will show normally
10+
/// - Multiple covers can be stacked with proper hierarchy management
11+
///
12+
/// - Parameters:
13+
/// - isPresented: Binding to control when the full screen cover is shown
14+
/// - onDismiss: Optional closure called when the cover is dismissed
15+
/// - content: Content to show in full screen
16+
/// - Returns: a modified view that shows full screen content with automatic modal hierarchy
17+
// periphery:ignore
18+
func posFullScreenCover<Content: View>(
19+
isPresented: Binding<Bool>,
20+
onDismiss: (() -> Void)? = nil,
21+
@ViewBuilder content: @escaping () -> Content
22+
) -> some View {
23+
self.modifier(
24+
POSFullScreenCoverModifier(
25+
isPresented: isPresented,
26+
onDismiss: onDismiss,
27+
coverContent: content
28+
)
29+
)
30+
}
31+
32+
/// - Parameters:
33+
/// - item: Binding to control when the full screen cover is shown
34+
/// - onDismiss: Optional closure called when the cover is dismissed
35+
/// - content: Content to show in full screen
36+
/// - Returns: a modified view that shows full screen content with automatic modal hierarchy
37+
// periphery:ignore
38+
func posFullScreenCover<Item: Identifiable & Equatable, Content: View>(
39+
item: Binding<Item?>,
40+
onDismiss: (() -> Void)? = nil,
41+
@ViewBuilder content: @escaping (Item) -> Content
42+
) -> some View {
43+
self.modifier(
44+
POSFullScreenCoverModifierForItem(
45+
item: item,
46+
onDismiss: onDismiss,
47+
coverContent: content
48+
)
49+
)
50+
}
51+
}
52+
53+
final class POSFullScreenCoverManager: ObservableObject {
54+
@Published fileprivate(set) var isPresented: Bool = false
55+
}
56+
57+
// MARK: - Modifiers
58+
// periphery:ignore
59+
struct POSFullScreenCoverModifier<CoverContent: View>: ViewModifier {
60+
@Binding var isPresented: Bool
61+
let onDismiss: (() -> Void)?
62+
let coverContent: () -> CoverContent
63+
64+
@EnvironmentObject var parentCoverManager: POSFullScreenCoverManager
65+
@StateObject private var modalManager = POSModalManager()
66+
@StateObject private var sheetManager = POSSheetManager()
67+
@StateObject private var coverManager = POSFullScreenCoverManager()
68+
69+
@State private var sheetId = UUID().uuidString
70+
71+
func body(content: Content) -> some View {
72+
content
73+
.fullScreenCover(isPresented: $isPresented, onDismiss: onDismiss, content: {
74+
coverContent()
75+
.posRootModal()
76+
.environmentObject(modalManager)
77+
.environmentObject(sheetManager)
78+
.environmentObject(coverManager)
79+
})
80+
.onChange(of: isPresented) { newValue in
81+
parentCoverManager.isPresented = newValue
82+
}
83+
}
84+
}
85+
// periphery:ignore
86+
struct POSFullScreenCoverModifierForItem<Item: Identifiable & Equatable, CoverContent: View>: ViewModifier {
87+
@Binding var item: Item?
88+
let onDismiss: (() -> Void)?
89+
let coverContent: (Item) -> CoverContent
90+
91+
@EnvironmentObject var parentCoverManager: POSFullScreenCoverManager
92+
@StateObject private var modalManager = POSModalManager()
93+
@StateObject private var sheetManager = POSSheetManager()
94+
@StateObject private var coverManager = POSFullScreenCoverManager()
95+
96+
@State private var sheetId = UUID().uuidString
97+
98+
func body(content: Content) -> some View {
99+
content
100+
.fullScreenCover(item: $item, onDismiss: onDismiss, content: {
101+
coverContent($0)
102+
.posRootModal()
103+
.environmentObject(modalManager)
104+
.environmentObject(sheetManager)
105+
.environmentObject(coverManager)
106+
})
107+
.onChange(of: item) { newValue in
108+
parentCoverManager.isPresented = newValue != nil
109+
}
110+
}
111+
}

WooCommerce/Classes/POS/Presentation/Reusable Views/POSModalViewModifier.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,17 @@ extension View {
7474

7575
struct POSModalViewModifier<Item: Identifiable & Equatable, ModalContent: View>: ViewModifier {
7676
@EnvironmentObject var modalManager: POSModalManager
77+
@EnvironmentObject var coverManager: POSFullScreenCoverManager
7778
@Binding var item: Item?
7879
let onDismiss: (() -> Void)?
7980
let modalContent: (Item) -> ModalContent
8081

8182
func body(content: Content) -> some View {
8283
content
8384
.onChange(of: item) { newItem in
85+
// Don't show a modal if a full screen overlay is presented on top
86+
guard !coverManager.isPresented else { return }
87+
8488
if let newItem = newItem {
8589
modalManager.present(onDismiss: {
8690
// Internal dismissal, i.e. from tapping the background
@@ -100,13 +104,17 @@ struct POSModalViewModifier<Item: Identifiable & Equatable, ModalContent: View>:
100104

101105
struct POSModalViewModifierForBool<ModalContent: View>: ViewModifier {
102106
@EnvironmentObject var modalManager: POSModalManager
107+
@EnvironmentObject var coverManager: POSFullScreenCoverManager
103108
@Binding var isPresented: Bool
104109
let onDismiss: (() -> Void)?
105110
let modalContent: () -> ModalContent
106111

107112
func body(content: Content) -> some View {
108113
content
109114
.onChange(of: isPresented) { newValue in
115+
// Don't show a modal if a full screen overlay is presented on top
116+
guard !coverManager.isPresented else { return }
117+
110118
if newValue {
111119
modalManager.present(onDismiss: {
112120
// Internal dismissal, i.e. from tapping the background

WooCommerce/Classes/POS/Presentation/Reusable Views/POSSheet.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,25 @@ final class POSSheetManager: ObservableObject {
3232

3333
struct POSSheetViewModifier<SheetContent: View>: ViewModifier {
3434
@EnvironmentObject var sheetManager: POSSheetManager
35+
@EnvironmentObject var coverManager: POSFullScreenCoverManager
3536
@Binding var isPresented: Bool
3637
let onDismiss: (() -> Void)?
3738
let sheetContent: () -> SheetContent
3839

3940
@State private var sheetId = UUID().uuidString
4041

42+
private var sheetIsPresented: Binding<Bool> {
43+
Binding<Bool>(get: {
44+
// Don't show a sheet if a full screen overlay is presented on top
45+
return self.$isPresented.wrappedValue && !coverManager.isPresented
46+
}, set: {
47+
self.$isPresented.wrappedValue = $0
48+
})
49+
}
50+
4151
func body(content: Content) -> some View {
4252
content
43-
.sheet(isPresented: $isPresented, onDismiss: onDismiss, content: sheetContent)
53+
.sheet(isPresented: sheetIsPresented, onDismiss: onDismiss, content: sheetContent)
4454
.onChange(of: isPresented) { newValue in
4555
if newValue {
4656
sheetManager.registerSheetPresented(id: sheetId)
@@ -53,16 +63,27 @@ struct POSSheetViewModifier<SheetContent: View>: ViewModifier {
5363

5464
struct POSSheetViewModifierForItem<Item: Identifiable & Equatable, SheetContent: View>: ViewModifier {
5565
@EnvironmentObject var sheetManager: POSSheetManager
66+
@EnvironmentObject var coverManager: POSFullScreenCoverManager
5667
@Binding var item: Item?
5768
let onDismiss: (() -> Void)?
5869
let sheetContent: (Item) -> SheetContent
5970

6071
@State private var sheetId = UUID().uuidString
6172

73+
private var sheetItem: Binding<Item?> {
74+
Binding<Item?>(get: {
75+
// Don't show a sheet if a full screen overlay is presented on top
76+
guard !coverManager.isPresented else { return nil }
77+
return self.$item.wrappedValue
78+
}, set: {
79+
self.$item.wrappedValue = $0
80+
})
81+
}
82+
6283
func body(content: Content) -> some View {
6384
content
64-
.sheet(item: $item, onDismiss: onDismiss, content: sheetContent)
65-
.onChange(of: item) { newItem in
85+
.sheet(item: sheetItem, onDismiss: onDismiss, content: sheetContent)
86+
.onChange(of: sheetItem.wrappedValue) { newItem in
6687
let newValue = newItem != nil
6788
if newValue {
6889
sheetManager.registerSheetPresented(id: sheetId)

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
0196FF942DA8067A0063CEF1 /* CouponCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0196FF932DA806720063CEF1 /* CouponCardView.swift */; };
7777
019A86842D89C13800ABBB71 /* TapToPayCardReaderPaymentAlertsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019A86832D89C13800ABBB71 /* TapToPayCardReaderPaymentAlertsProvider.swift */; };
7878
01A3093C2DAE768600B672F6 /* MockPointOfSaleCouponService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A3093B2DAE768000B672F6 /* MockPointOfSaleCouponService.swift */; };
79+
01AA4FA12E4CB22900FA9B4C /* POSFullScreenCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AA4FA02E4CB22700FA9B4C /* POSFullScreenCover.swift */; };
7980
01AAD8142D92E37A0081D60B /* PointOfSaleOrderSyncCouponsErrorMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AAD8132D92E37A0081D60B /* PointOfSaleOrderSyncCouponsErrorMessageView.swift */; };
8081
01AB2D122DDC7AD300AA67FD /* PointOfSaleItemListAnalyticsTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AB2D112DDC7AD100AA67FD /* PointOfSaleItemListAnalyticsTrackerTests.swift */; };
8182
01AB2D142DDC7CD200AA67FD /* POSItemActionHandlerFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AB2D132DDC7CD000AA67FD /* POSItemActionHandlerFactoryTests.swift */; };
@@ -3267,6 +3268,7 @@
32673268
0196FF932DA806720063CEF1 /* CouponCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponCardView.swift; sourceTree = "<group>"; };
32683269
019A86832D89C13800ABBB71 /* TapToPayCardReaderPaymentAlertsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapToPayCardReaderPaymentAlertsProvider.swift; sourceTree = "<group>"; };
32693270
01A3093B2DAE768000B672F6 /* MockPointOfSaleCouponService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPointOfSaleCouponService.swift; sourceTree = "<group>"; };
3271+
01AA4FA02E4CB22700FA9B4C /* POSFullScreenCover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSFullScreenCover.swift; sourceTree = "<group>"; };
32703272
01AAD8132D92E37A0081D60B /* PointOfSaleOrderSyncCouponsErrorMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleOrderSyncCouponsErrorMessageView.swift; sourceTree = "<group>"; };
32713273
01AB2D112DDC7AD100AA67FD /* PointOfSaleItemListAnalyticsTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleItemListAnalyticsTrackerTests.swift; sourceTree = "<group>"; };
32723274
01AB2D132DDC7CD000AA67FD /* POSItemActionHandlerFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSItemActionHandlerFactoryTests.swift; sourceTree = "<group>"; };
@@ -6447,8 +6449,9 @@
64476449
01620C4C2C5394A400D3EA2F /* Reusable Views */ = {
64486450
isa = PBXGroup;
64496451
children = (
6450-
016DE5322E40B03200F53DF7 /* POSSheet.swift */,
64516452
0210A2452D55EC140054C78D /* Buttons */,
6453+
016DE5322E40B03200F53DF7 /* POSSheet.swift */,
6454+
01AA4FA02E4CB22700FA9B4C /* POSFullScreenCover.swift */,
64526455
01620C4D2C5394B200D3EA2F /* POSProgressViewStyle.swift */,
64536456
204D1D612C5A50840064A6BE /* POSModalViewModifier.swift */,
64546457
20D2CCA42C7E328300051705 /* POSModalCloseButton.swift */,
@@ -16201,6 +16204,7 @@
1620116204
0212276124498A270042161F /* ProductFormBottomSheetListSelectorCommand.swift in Sources */,
1620216205
D831E2E0230E0BA7000037D0 /* Logs.swift in Sources */,
1620316206
02CEBB8224C98861002EDF35 /* ProductFormDataModel.swift in Sources */,
16207+
01AA4FA12E4CB22900FA9B4C /* POSFullScreenCover.swift in Sources */,
1620416208
3120491B26DD80E000A4EC4F /* ActivitySpinnerAndLabelTableViewCell.swift in Sources */,
1620516209
DEC51AFD276AEAE3009F3DF4 /* SystemStatusReportView.swift in Sources */,
1620616210
CECC759C23D61C1400486676 /* AggregateDataHelper.swift in Sources */,

0 commit comments

Comments
 (0)