Skip to content

Commit 7dff630

Browse files
committed
POS Modularization: Move Coupon and Discount creation views to the adaptor layer
Coupon and Discount creation views belong to the Woo app target and contain a lot of Woo app dependencies that are hard to refactor. As a workaround, move the sheet creator to the adaptor layer and access type erased views through externalViews environment to build POSCouponCreationSheet.
1 parent 88974ef commit 7dff630

File tree

6 files changed

+149
-74
lines changed

6 files changed

+149
-74
lines changed

WooCommerce/Classes/POS/Adaptors/POSServiceLocatorAdaptor.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import Yosemite
55
import protocol Experiments.FeatureFlagService
66
import enum Experiments.FeatureFlag
77
import protocol Storage.StorageManagerType
8-
98
final class POSServiceLocatorAdaptor: POSDependencyProviding {
109
var analytics: POSAnalyticsProviding {
1110
POSAnalyticsAdaptor()
@@ -90,4 +89,30 @@ private struct POSExternalViewAdaptor: POSExternalViewProviding {
9089
onChange: @escaping (String) -> Void) -> AnyView {
9190
AnyView(POSFormattableAmountTextFieldAdaptor(preset: preset, onSubmit: onSubmit, onChange: onChange))
9291
}
92+
93+
func createCouponCreationView(discountType: Coupon.DiscountType,
94+
showTypeSelection: Binding<Bool>,
95+
onSuccess: @escaping (Coupon) -> Void,
96+
dismissHandler: @escaping () -> Void,
97+
onDisappear: @escaping () -> Void) -> AnyView {
98+
AnyView(POSCouponCreationViewAdaptor(
99+
discountType: discountType,
100+
showTypeSelection: showTypeSelection,
101+
onSuccess: onSuccess,
102+
dismissHandler: dismissHandler,
103+
onDisappear: onDisappear
104+
))
105+
}
106+
107+
func createDiscountTypeSelectionSheet(isPresented: Binding<Bool>,
108+
title: String,
109+
cancelButtonTitle: String,
110+
onSelection: @escaping (Coupon.DiscountType) -> Void) -> AnyView {
111+
AnyView(POSDiscountTypeSelectionSheetAdaptor(
112+
isPresented: isPresented,
113+
title: title,
114+
cancelButtonTitle: cancelButtonTitle,
115+
onSelection: onSelection
116+
))
117+
}
93118
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import SwiftUI
2+
import struct Yosemite.Coupon
3+
4+
/// Provide access to AddEditCouponView and ViewModel for POS without making it as an explicit type dependency
5+
/// AddEditCouponView cannot be easily moved and reused in a shared module due to multiple dependencies
6+
/// This is used as a workaround to enable POS modularization without requiring a larger refactoring effort
7+
///
8+
9+
struct POSCouponCreationViewAdaptor: View {
10+
@StateObject private var viewModel: AddEditCouponViewModel
11+
@Binding var showTypeSelection: Bool
12+
let dismissHandler: () -> Void
13+
let onDisappear: () -> Void
14+
15+
init(discountType: Coupon.DiscountType,
16+
showTypeSelection: Binding<Bool>,
17+
onSuccess: @escaping (Coupon) -> Void,
18+
dismissHandler: @escaping () -> Void,
19+
onDisappear: @escaping () -> Void) {
20+
_showTypeSelection = showTypeSelection
21+
self.dismissHandler = dismissHandler
22+
self.onDisappear = onDisappear
23+
_viewModel = StateObject(wrappedValue: AddEditCouponViewModel(
24+
discountType: discountType,
25+
onSuccess: onSuccess
26+
))
27+
}
28+
29+
var body: some View {
30+
var view = AddEditCoupon(viewModel)
31+
view.dismissHandler = dismissHandler
32+
view.onDisappear = onDisappear
33+
view.discountTypeHandler = { _ in
34+
showTypeSelection = true
35+
}
36+
37+
return view
38+
.interactiveDismissDisabled()
39+
}
40+
}
41+
42+
struct POSDiscountTypeSelectionSheetAdaptor: View {
43+
@Binding var isPresented: Bool
44+
let title: String
45+
let cancelButtonTitle: String
46+
let onSelection: (Coupon.DiscountType) -> Void
47+
48+
var body: some View {
49+
let command = DiscountTypeBottomSheetListSelectorCommand(selected: nil) { type in
50+
onSelection(type)
51+
}
52+
53+
NavigationView {
54+
BottomSheetListSelector(
55+
viewProperties: BottomSheetListSelectorViewProperties(),
56+
command: command,
57+
onDismiss: { _ in
58+
isPresented = false
59+
}
60+
)
61+
.navigationBarTitleDisplayMode(.large)
62+
.navigationTitle(title)
63+
.toolbar {
64+
ToolbarItem(placement: .cancellationAction) {
65+
Button(cancelButtonTitle) {
66+
isPresented = false
67+
}
68+
}
69+
}
70+
}
71+
.navigationViewStyle(.stack)
72+
.interactiveDismissDisabled()
73+
}
74+
}

WooCommerce/Classes/POS/Presentation/Coupons/POSCouponCreationSheet.swift

Lines changed: 19 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ private struct POSCouponCreationSheetModifier: ViewModifier {
1616
@Binding var isPresented: Bool
1717
let onSuccess: (POSItem) -> Void
1818

19+
@Environment(\.posExternalViews) private var externalViews
1920
@State private var selectedType: POSCouponDiscountType?
2021
@State private var showCouponSelectionSheet: Bool = false
2122
@State private var addedCouponItem: POSItem?
2223

2324
func body(content: Content) -> some View {
2425
content
2526
.posSheet(item: $selectedType) { (posDiscountType: POSCouponDiscountType) in
26-
POSCouponCreationView(
27+
externalViews.createCouponCreationView(
2728
discountType: posDiscountType.discountType,
2829
showTypeSelection: $showCouponSelectionSheet,
2930
onSuccess: { coupon in
@@ -40,81 +41,26 @@ private struct POSCouponCreationSheetModifier: ViewModifier {
4041
}
4142
}
4243
)
43-
}
44-
.discountTypeSelectionSheet(isPresented: $isPresented) { type in
45-
selectedType = type
46-
}
47-
}
48-
}
49-
50-
private struct POSCouponCreationView: View {
51-
@StateObject private var viewModel: AddEditCouponViewModel
52-
@Binding var showTypeSelection: Bool
53-
let dismissHandler: () -> Void
54-
let onDisappear: () -> Void
55-
56-
init(discountType: Coupon.DiscountType,
57-
showTypeSelection: Binding<Bool>,
58-
onSuccess: @escaping (Coupon) -> Void,
59-
dismissHandler: @escaping () -> Void,
60-
onDisappear: @escaping () -> Void) {
61-
_showTypeSelection = showTypeSelection
62-
self.dismissHandler = dismissHandler
63-
self.onDisappear = onDisappear
64-
_viewModel = StateObject(wrappedValue: AddEditCouponViewModel(
65-
discountType: discountType,
66-
onSuccess: onSuccess
67-
))
68-
}
69-
70-
var body: some View {
71-
var view = AddEditCoupon(viewModel)
72-
view.dismissHandler = dismissHandler
73-
view.onDisappear = onDisappear
74-
view.discountTypeHandler = { _ in
75-
showTypeSelection = true
76-
}
77-
78-
return view
79-
.interactiveDismissDisabled()
80-
.discountTypeSelectionSheet(isPresented: $showTypeSelection) { type in
81-
showTypeSelection = false
82-
viewModel.discountType = type.discountType
83-
}
84-
}
85-
}
86-
87-
private extension View {
88-
func discountTypeSelectionSheet(
89-
isPresented: Binding<Bool>,
90-
onSelection: @escaping (POSCouponDiscountType) -> Void
91-
) -> some View {
92-
posSheet(isPresented: isPresented) {
93-
let command = DiscountTypeBottomSheetListSelectorCommand(selected: nil) { type in
94-
onSelection(.init(discountType: type))
95-
}
96-
97-
NavigationView {
98-
BottomSheetListSelector(
99-
viewProperties: BottomSheetListSelectorViewProperties(),
100-
command: command,
101-
onDismiss: { _ in
102-
isPresented.wrappedValue = false
103-
}
104-
)
105-
.navigationBarTitleDisplayMode(.large)
106-
.navigationTitle(Localization.selectCouponTypeTitle)
107-
.toolbar {
108-
ToolbarItem(placement: .cancellationAction) {
109-
Button(Localization.selectCouponCancelButtonTitle) {
110-
isPresented.wrappedValue = false
111-
}
44+
.posSheet(isPresented: $showCouponSelectionSheet) {
45+
externalViews.createDiscountTypeSelectionSheet(
46+
isPresented: $showCouponSelectionSheet,
47+
title: Localization.selectCouponTypeTitle,
48+
cancelButtonTitle: Localization.selectCouponCancelButtonTitle
49+
) { type in
50+
showCouponSelectionSheet = false
51+
selectedType = POSCouponDiscountType(discountType: type)
11252
}
11353
}
11454
}
115-
.navigationViewStyle(.stack)
116-
.interactiveDismissDisabled()
117-
}
55+
.posSheet(isPresented: $isPresented) {
56+
externalViews.createDiscountTypeSelectionSheet(
57+
isPresented: $isPresented,
58+
title: Localization.selectCouponTypeTitle,
59+
cancelButtonTitle: Localization.selectCouponCancelButtonTitle
60+
) { type in
61+
selectedType = POSCouponDiscountType(discountType: type)
62+
}
63+
}
11864
}
11965
}
12066

WooCommerce/Classes/POS/Protocols/POSDependencyProviding.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import SwiftUI
22
import WooFoundationCore
33
import WooFoundation
44
import enum Experiments.FeatureFlag
5+
import struct Yosemite.Coupon
6+
import enum Yosemite.POSItem
57

68
/// POSDepenencyProviding is part of the POS entry point that defines the external dependencies from the Woo app that POS depends on
79

@@ -48,6 +50,15 @@ protocol POSExternalViewProviding {
4850
func createFormattableAmountTextField(preset: Decimal?,
4951
onSubmit: @escaping () -> Void,
5052
onChange: @escaping (String) -> Void) -> AnyView
53+
func createCouponCreationView(discountType: Coupon.DiscountType,
54+
showTypeSelection: Binding<Bool>,
55+
onSuccess: @escaping (Coupon) -> Void,
56+
dismissHandler: @escaping () -> Void,
57+
onDisappear: @escaping () -> Void) -> AnyView
58+
func createDiscountTypeSelectionSheet(isPresented: Binding<Bool>,
59+
title: String,
60+
cancelButtonTitle: String,
61+
onSelection: @escaping (Coupon.DiscountType) -> Void) -> AnyView
5162
}
5263

5364
/// Main protocol that combines all POS dependency providers

WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import WooFoundation
44
import Experiments
55
import protocol Yosemite.Action
66
import struct Yosemite.Site
7+
import enum Yosemite.POSItem
8+
import struct Yosemite.Coupon
79

810
struct SiteTimezoneKey: EnvironmentKey {
911
static let defaultValue: TimeZone = .current
@@ -127,5 +129,18 @@ struct EmptyPOSExternalView: POSExternalViewProviding {
127129
onChange: @escaping (String) -> Void) -> AnyView {
128130
AnyView(EmptyView())
129131
}
132+
func createCouponCreationView(discountType: Coupon.DiscountType,
133+
showTypeSelection: Binding<Bool>,
134+
onSuccess: @escaping (Coupon) -> Void,
135+
dismissHandler: @escaping () -> Void,
136+
onDisappear: @escaping () -> Void) -> AnyView {
137+
AnyView(EmptyView())
138+
}
139+
func createDiscountTypeSelectionSheet(isPresented: Binding<Bool>,
140+
title: String,
141+
cancelButtonTitle: String,
142+
onSelection: @escaping (Coupon.DiscountType) -> Void) -> AnyView {
143+
AnyView(EmptyView())
144+
}
130145
init() {}
131146
}

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
01058DD02E42716A002FADD1 /* BarcodeScannerContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01058DCF2E42716A002FADD1 /* BarcodeScannerContainerTests.swift */; };
2727
01058DD22E4273F2002FADD1 /* UIKitBarcodeObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01058DD12E4273F2002FADD1 /* UIKitBarcodeObserverTests.swift */; };
2828
010F7D872E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010F7D862E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift */; };
29+
010F7D8B2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010F7D8A2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift */; };
2930
011D396F2D09FCD200DB1445 /* CardPresentModalLocationRequired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011D396E2D09FCCB00DB1445 /* CardPresentModalLocationRequired.swift */; };
3031
011D39712D0A324200DB1445 /* LocationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011D39702D0A324100DB1445 /* LocationServiceTests.swift */; };
3132
011D7A332CEC877A0007C187 /* CardPresentModalNonRetryableErrorEmailSent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011D7A322CEC87770007C187 /* CardPresentModalNonRetryableErrorEmailSent.swift */; };
@@ -3236,6 +3237,7 @@
32363237
01058DCF2E42716A002FADD1 /* BarcodeScannerContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarcodeScannerContainerTests.swift; sourceTree = "<group>"; };
32373238
01058DD12E4273F2002FADD1 /* UIKitBarcodeObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBarcodeObserverTests.swift; sourceTree = "<group>"; };
32383239
010F7D862E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSFormattableAmountTextFieldAdaptor.swift; sourceTree = "<group>"; };
3240+
010F7D8A2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCouponCreationSheetAdaptor.swift; sourceTree = "<group>"; };
32393241
011D396E2D09FCCB00DB1445 /* CardPresentModalLocationRequired.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalLocationRequired.swift; sourceTree = "<group>"; };
32403242
011D39702D0A324100DB1445 /* LocationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServiceTests.swift; sourceTree = "<group>"; };
32413243
011D7A322CEC87770007C187 /* CardPresentModalNonRetryableErrorEmailSent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalNonRetryableErrorEmailSent.swift; sourceTree = "<group>"; };
@@ -6480,6 +6482,7 @@
64806482
010F7D852E79B39A002B02EA /* View Adaptors */ = {
64816483
isa = PBXGroup;
64826484
children = (
6485+
010F7D8A2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift */,
64836486
010F7D862E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift */,
64846487
);
64856488
path = "View Adaptors";
@@ -16459,6 +16462,7 @@
1645916462
DEA66A1B2E41E0C000791018 /* BlazeCampaignProduct.swift in Sources */,
1646016463
CE6E110B2C91DA5D00563DD4 /* WooShippingItemRow.swift in Sources */,
1646116464
B946880E29B627EB000646B0 /* SearchableActivityConvertable.swift in Sources */,
16465+
010F7D8B2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift in Sources */,
1646216466
027ADB732D21812D009608DB /* POSItemImageView.swift in Sources */,
1646316467
EE09DE0B2C2D6E5100A32680 /* SelectPackageImageCoordinator.swift in Sources */,
1646416468
DE621F6A29D67E1B000DE3BD /* WooAnalyticsEvent+JetpackSetup.swift in Sources */,

0 commit comments

Comments
 (0)