Skip to content

Commit dba69c0

Browse files
authored
[Woo POS][Barcodes] Scanner set up flow structure (#15883)
2 parents 56cffaf + 5e405e7 commit dba69c0

10 files changed

+582
-233
lines changed

Modules/Sources/Experiments/DefaultFeatureFlagService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
102102
case .pointOfSaleOrdersi2:
103103
return buildConfig == .localDeveloper || buildConfig == .alpha
104104
case .pointOfSaleBarcodeScanningi2:
105-
return false
105+
return buildConfig == .localDeveloper || buildConfig == .alpha
106106
default:
107107
return true
108108
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import SwiftUI
2+
3+
@available(iOS 17.0, *)
4+
struct PointOfSaleBarcodeScannerSetup: View {
5+
@Binding var isPresented: Bool
6+
@State private var flowManager: PointOfSaleBarcodeScannerSetupFlowManager
7+
8+
init(isPresented: Binding<Bool>) {
9+
self._isPresented = isPresented
10+
self.flowManager = PointOfSaleBarcodeScannerSetupFlowManager(isPresented: isPresented)
11+
}
12+
13+
var body: some View {
14+
VStack(spacing: POSSpacing.xxLarge) {
15+
// Header
16+
PointOfSaleModalHeader(isPresented: $isPresented,
17+
title: .constant(AttributedString(currentTitle)))
18+
19+
VStack {
20+
currentContent
21+
Spacer()
22+
}
23+
.scrollVerticallyIfNeeded()
24+
25+
// Bottom buttons
26+
PointOfSaleFlowButtonsView(configuration: flowManager.buttonConfiguration)
27+
}
28+
.padding(POSPadding.xxLarge)
29+
.background(Color.posSurfaceBright)
30+
.containerRelativeFrame([.horizontal, .vertical]) { length, _ in
31+
max(length * 0.75, Constants.modalFrameMaxSmallDimension)
32+
}
33+
.onAppear {
34+
ServiceLocator.analytics.track(.pointOfSaleBarcodeScannerSetupFlowShown)
35+
}
36+
}
37+
38+
// MARK: - Computed Properties
39+
private var currentTitle: String {
40+
switch flowManager.currentState {
41+
case .scannerSelection:
42+
return Localization.setupHeading
43+
case .setupFlow:
44+
return flowManager.getCurrentStep()?.title ?? Localization.setupHeading
45+
}
46+
}
47+
48+
@ViewBuilder
49+
private var currentContent: some View {
50+
switch flowManager.currentState {
51+
case .scannerSelection:
52+
PointOfSaleBarcodeScannerSetupSelectionView(options: scannerOptions) { scannerType in
53+
flowManager.selectScanner(scannerType)
54+
}
55+
case .setupFlow:
56+
if let step = flowManager.getCurrentStep() {
57+
AnyView(step.content)
58+
}
59+
}
60+
}
61+
62+
private var scannerOptions: [PointOfSaleBarcodeScannerSetupFlowOption] {
63+
[
64+
PointOfSaleBarcodeScannerSetupFlowOption(
65+
title: Localization.socketS720Title,
66+
subtitle: Localization.socketS720Subtitle,
67+
scannerType: .socketS720
68+
),
69+
PointOfSaleBarcodeScannerSetupFlowOption(
70+
title: Localization.starBSH20BTitle,
71+
subtitle: Localization.starBSH20BSubtitle,
72+
scannerType: .starBSH20B
73+
),
74+
PointOfSaleBarcodeScannerSetupFlowOption(
75+
title: Localization.tbcScannerTitle,
76+
subtitle: Localization.tbcScannerSubtitle,
77+
scannerType: .tbcScanner
78+
),
79+
PointOfSaleBarcodeScannerSetupFlowOption(
80+
title: Localization.otherTitle,
81+
subtitle: Localization.otherSubtitle,
82+
scannerType: .other
83+
)
84+
]
85+
}
86+
}
87+
88+
// MARK: - Constants
89+
private enum Constants {
90+
static var modalFrameMaxSmallDimension: CGFloat { 752 }
91+
}
92+
93+
// MARK: - Private Localization Extension
94+
@available(iOS 17.0, *)
95+
private extension PointOfSaleBarcodeScannerSetup {
96+
enum Localization {
97+
static let setupHeading = NSLocalizedString(
98+
"pos.barcodeScannerSetup.heading",
99+
value: "Barcode Scanner Setup",
100+
comment: "Heading for the barcode scanner setup flow in POS"
101+
)
102+
103+
static let socketS720Title = NSLocalizedString(
104+
"pos.barcodeScannerSetup.socketS720.title",
105+
value: "Socket S720",
106+
comment: "Title for Socket S720 scanner option in barcode scanner setup"
107+
)
108+
static let socketS720Subtitle = NSLocalizedString(
109+
"pos.barcodeScannerSetup.socketS720.subtitle",
110+
value: "Small handheld scanner with a charging dock or stand",
111+
comment: "Subtitle for Socket S720 scanner option in barcode scanner setup"
112+
)
113+
static let starBSH20BTitle = NSLocalizedString(
114+
"pos.barcodeScannerSetup.starBSH20B.title",
115+
value: "Star BSH-20B",
116+
comment: "Title for Star BSH-20B scanner option in barcode scanner setup"
117+
)
118+
static let starBSH20BSubtitle = NSLocalizedString(
119+
"pos.barcodeScannerSetup.starBSH20B.subtitle",
120+
value: "Ergonomic scanner with a stand",
121+
comment: "Subtitle for Star BSH-20B scanner option in barcode scanner setup"
122+
)
123+
static let tbcScannerTitle = NSLocalizedString(
124+
"pos.barcodeScannerSetup.tbcScanner.title",
125+
value: "Scanner TBC",
126+
comment: "Title for TBC scanner option in barcode scanner setup"
127+
)
128+
static let tbcScannerSubtitle = NSLocalizedString(
129+
"pos.barcodeScannerSetup.tbcScanner.subtitle",
130+
value: "Recommended scanner",
131+
comment: "Subtitle for TBC scanner option in barcode scanner setup"
132+
)
133+
static let otherTitle = NSLocalizedString(
134+
"pos.barcodeScannerSetup.other.title",
135+
value: "Other",
136+
comment: "Title for other scanner option in barcode scanner setup"
137+
)
138+
static let otherSubtitle = NSLocalizedString(
139+
"pos.barcodeScannerSetup.other.subtitle",
140+
value: "General scanner setup instructions",
141+
comment: "Subtitle for other scanner option in barcode scanner setup"
142+
)
143+
}
144+
}
145+
146+
@available(iOS 17.0, *)
147+
#Preview {
148+
PointOfSaleBarcodeScannerSetup(isPresented: .constant(true))
149+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import SwiftUI
2+
3+
// MARK: - Point of Sale Barcode Scanner Setup Flow
4+
@available(iOS 17.0, *)
5+
@Observable
6+
class PointOfSaleBarcodeScannerSetupFlow {
7+
private let scannerType: PointOfSaleBarcodeScannerType
8+
private let onComplete: () -> Void
9+
private let onBackToSelection: () -> Void
10+
private var currentStepIndex: Int = 0
11+
12+
init(scannerType: PointOfSaleBarcodeScannerType,
13+
onComplete: @escaping () -> Void,
14+
onBackToSelection: @escaping () -> Void) {
15+
self.scannerType = scannerType
16+
self.onComplete = onComplete
17+
self.onBackToSelection = onBackToSelection
18+
}
19+
20+
var currentStep: PointOfSaleBarcodeScannerSetupStep? {
21+
steps[safe: currentStepIndex]
22+
}
23+
24+
var isComplete: Bool {
25+
currentStepIndex >= steps.count - 1
26+
}
27+
28+
var nextButtonTitle: String {
29+
isComplete ? Localization.doneButtonTitle : Localization.nextButtonTitle
30+
}
31+
32+
func nextStep() {
33+
if currentStepIndex < steps.count - 1 {
34+
currentStepIndex += 1
35+
} else {
36+
onComplete()
37+
}
38+
}
39+
40+
func previousStep() {
41+
if currentStepIndex > 0 {
42+
currentStepIndex -= 1
43+
} else {
44+
onBackToSelection()
45+
}
46+
}
47+
48+
func restartFlow() {
49+
currentStepIndex = 0
50+
}
51+
52+
func getButtonConfiguration() -> PointOfSaleFlowButtonConfiguration {
53+
guard let step = currentStep else {
54+
return .noButtons()
55+
}
56+
57+
// Use step customization if available
58+
if let customization = step.buttonCustomization {
59+
return customization.customizeButtons(for: self)
60+
}
61+
62+
// Default button configuration
63+
return PointOfSaleFlowButtonConfiguration(
64+
primaryButton: PointOfSaleFlowButtonConfiguration.ButtonConfig(
65+
title: nextButtonTitle,
66+
action: { [weak self] in
67+
self?.nextStep()
68+
}
69+
),
70+
secondaryButton: PointOfSaleFlowButtonConfiguration.ButtonConfig(
71+
title: Localization.backButtonTitle,
72+
action: { [weak self] in
73+
self?.previousStep()
74+
}
75+
)
76+
)
77+
}
78+
79+
private var steps: [PointOfSaleBarcodeScannerSetupStep] {
80+
switch scannerType {
81+
case .socketS720:
82+
return [
83+
createWelcomeStep(title: "Socket S720 Setup")
84+
// TODO: Add more steps for Socket S720 WOOMOB-698
85+
]
86+
case .starBSH20B:
87+
return [
88+
createWelcomeStep(title: "Star BSH-20B Setup")
89+
// TODO: Add more steps for Star BSH-20B WOOMOB-696
90+
]
91+
case .tbcScanner:
92+
return [
93+
createWelcomeStep(title: "TBC Scanner Setup")
94+
// TODO: Add more steps for TBC Scanner WOOMOB-699
95+
]
96+
case .other:
97+
return [
98+
PointOfSaleBarcodeScannerSetupStep(
99+
title: "General Scanner Setup",
100+
content: { BarcodeScannerInformationContent() }
101+
)
102+
]
103+
}
104+
}
105+
106+
private func createWelcomeStep(title: String) -> PointOfSaleBarcodeScannerSetupStep {
107+
PointOfSaleBarcodeScannerSetupStep(
108+
title: title,
109+
content: { PointOfSaleBarcodeScannerWelcomeView(title: title) },
110+
buttonCustomization: PointOfSaleBarcodeScannerWelcomeButtonCustomization()
111+
)
112+
}
113+
}
114+
115+
// MARK: - Button Customizations
116+
@available(iOS 17.0, *)
117+
struct PointOfSaleBarcodeScannerWelcomeButtonCustomization: PointOfSaleBarcodeScannerButtonCustomization {
118+
func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration {
119+
return PointOfSaleFlowButtonConfiguration(
120+
primaryButton: PointOfSaleFlowButtonConfiguration.ButtonConfig(
121+
title: Localization.doneButtonTitle,
122+
action: { flow.nextStep() }
123+
),
124+
secondaryButton: nil
125+
)
126+
}
127+
128+
private enum Localization {
129+
static let doneButtonTitle = NSLocalizedString(
130+
"pos.barcodeScannerSetup.done.button.title",
131+
value: "Done",
132+
comment: "Title for the done button in barcode scanner setup navigation"
133+
)
134+
}
135+
}
136+
137+
// MARK: - Private Localization Extension
138+
@available(iOS 17.0, *)
139+
private extension PointOfSaleBarcodeScannerSetupFlow {
140+
enum Localization {
141+
static let doneButtonTitle = NSLocalizedString(
142+
"pos.barcodeScannerSetup.done.button.title",
143+
value: "Done",
144+
comment: "Title for the done button in barcode scanner setup navigation"
145+
)
146+
static let nextButtonTitle = NSLocalizedString(
147+
"pos.barcodeScannerSetup.next.button.title",
148+
value: "Next",
149+
comment: "Title for the next button in barcode scanner setup navigation"
150+
)
151+
static let backButtonTitle = NSLocalizedString(
152+
"pos.barcodeScannerSetup.back.button.title",
153+
value: "Back",
154+
comment: "Title for the back button in barcode scanner setup navigation"
155+
)
156+
}
157+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import SwiftUI
2+
3+
// MARK: - Point of Sale Barcode Scanner Setup Flow Manager
4+
@available(iOS 17.0, *)
5+
@Observable
6+
class PointOfSaleBarcodeScannerSetupFlowManager {
7+
var currentState: PointOfSaleBarcodeScannerSetupFlowState = .scannerSelection
8+
@ObservationIgnored @Binding var isPresented: Bool
9+
private var currentFlow: PointOfSaleBarcodeScannerSetupFlow?
10+
11+
init(isPresented: Binding<Bool>) {
12+
self._isPresented = isPresented
13+
}
14+
15+
func selectScanner(_ scannerType: PointOfSaleBarcodeScannerType) {
16+
currentFlow = PointOfSaleBarcodeScannerSetupFlow(scannerType: scannerType, onComplete: { [weak self] in
17+
self?.isPresented = false
18+
}, onBackToSelection: { [weak self] in
19+
self?.goBackToSelection()
20+
})
21+
currentState = .setupFlow(scannerType)
22+
}
23+
24+
func goBackToSelection() {
25+
currentState = .scannerSelection
26+
currentFlow = nil
27+
}
28+
29+
func nextStep() {
30+
currentFlow?.nextStep()
31+
}
32+
33+
func previousStep() {
34+
currentFlow?.previousStep()
35+
}
36+
37+
func getCurrentStep() -> PointOfSaleBarcodeScannerSetupStep? {
38+
currentFlow?.currentStep
39+
}
40+
41+
func isComplete() -> Bool {
42+
currentFlow?.isComplete ?? false
43+
}
44+
45+
var buttonConfiguration: PointOfSaleFlowButtonConfiguration {
46+
switch currentState {
47+
case .scannerSelection:
48+
return .noButtons()
49+
case .setupFlow:
50+
guard let flow = currentFlow else {
51+
return .noButtons()
52+
}
53+
54+
return flow.getButtonConfiguration()
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)