Skip to content

Commit b0430e4

Browse files
authored
[Woo POS][Barcodes] Set up flow: choose a scanner (#15882)
2 parents 5b02539 + 8c05230 commit b0430e4

File tree

10 files changed

+292
-21
lines changed

10 files changed

+292
-21
lines changed

Modules/Sources/Experiments/DefaultFeatureFlagService.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
101101
return buildConfig == .localDeveloper || buildConfig == .alpha
102102
case .pointOfSaleOrdersi2:
103103
return buildConfig == .localDeveloper || buildConfig == .alpha
104+
case .pointOfSaleBarcodeScanningi2:
105+
return false
104106
default:
105107
return true
106108
}

Modules/Sources/Experiments/FeatureFlag.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,8 @@ public enum FeatureFlag: Int {
207207
/// Enables displaying Point Of Sale as a filter in order list
208208
///
209209
case pointOfSaleOrdersi2
210+
211+
/// Enables the Point of Sale Barcode Scanner set up flows, as part of i2
212+
///
213+
case pointOfSaleBarcodeScanningi2
210214
}

WooCommerce/Classes/Analytics/TracksProvider.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ private extension TracksProvider {
150150
WooAnalyticsStat.pointOfSaleSearchRemoteResultsFetched,
151151
WooAnalyticsStat.pointOfSaleBarcodeScanningMenuItemTapped,
152152
WooAnalyticsStat.pointOfSaleBarcodeScanningExplanationDialogShown,
153+
WooAnalyticsStat.pointOfSaleBarcodeScannerSetupFlowShown,
153154

154155
// Order
155156
WooAnalyticsStat.orderCreationSuccess,

WooCommerce/Classes/Analytics/WooAnalyticsStat.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,7 @@ enum WooAnalyticsStat: String {
13051305
case pointOfSaleSearchRemoteResultsFetched = "search_remote_results_fetched"
13061306
case pointOfSaleBarcodeScanningMenuItemTapped = "barcode_scanning_menu_item_tapped"
13071307
case pointOfSaleBarcodeScanningExplanationDialogShown = "barcode_scanning_explanation_dialog_shown"
1308+
case pointOfSaleBarcodeScannerSetupFlowShown = "barcode_scanner_setup_flow_shown"
13081309

13091310
// MARK: Custom Fields events
13101311
case productDetailCustomFieldsTapped = "product_detail_custom_fields_tapped"

WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct POSFloatingControlView: View {
99
@Binding private var showSupport: Bool
1010
@Binding private var showDocumentation: Bool
1111
@State private var showProductRestrictionsModal: Bool = false
12-
@State private var showBarcodeScanningInformation: Bool = false
12+
@State private var showBarcodeScanningModal: Bool = false
1313

1414
init(showExitPOSModal: Binding<Bool>,
1515
showSupport: Binding<Bool>,
@@ -59,7 +59,7 @@ struct POSFloatingControlView: View {
5959
}
6060
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.pointOfSaleBarcodeScanningi1) {
6161
Button {
62-
showBarcodeScanningInformation = true
62+
showBarcodeScanningModal = true
6363
ServiceLocator.analytics.track(.pointOfSaleBarcodeScanningMenuItemTapped)
6464
} label: {
6565
Label(
@@ -92,8 +92,12 @@ struct POSFloatingControlView: View {
9292
.posModal(isPresented: $showProductRestrictionsModal) {
9393
SimpleProductsOnlyInformation(isPresented: $showProductRestrictionsModal)
9494
}
95-
.posModal(isPresented: $showBarcodeScanningInformation) {
96-
PointOfSaleBarcodeScannerInformationModal(isPresented: $showBarcodeScanningInformation)
95+
.posModal(isPresented: $showBarcodeScanningModal) {
96+
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.pointOfSaleBarcodeScanningi2) {
97+
PointOfSaleBarcodeScannerSetUpFlow(isPresented: $showBarcodeScanningModal)
98+
} else {
99+
PointOfSaleBarcodeScannerInformationModal(isPresented: $showBarcodeScanningModal)
100+
}
97101
}
98102
.frame(height: Constants.size)
99103
.background(Color.clear)

WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerInformationModal.swift

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ struct PointOfSaleBarcodeScannerInformationModal: View {
1010

1111
var body: some View {
1212
PointOfSaleInformationModal(isPresented: $isPresented, title: AttributedString(Localization.barcodeInfoHeading)) {
13+
BarcodeScannerInformationContent()
14+
}
15+
}
16+
}
17+
18+
struct BarcodeScannerInformationContent: View {
19+
var body: some View {
20+
VStack(spacing: POSSpacing.medium) {
1321
PointOfSaleInformationModalParagraphView {
1422
Text(AttributedString(Localization.barcodeInfoIntroMessage))
1523
}
@@ -50,17 +58,22 @@ struct PointOfSaleBarcodeScannerInformationModal: View {
5058
}
5159
}
5260

53-
private extension PointOfSaleBarcodeScannerInformationModal {
54-
enum Constants {
55-
static let detailsLink = URL(string: "https://woocommerce.com/document/barcode-and-qr-code-scanner/")
56-
}
57-
61+
extension PointOfSaleBarcodeScannerInformationModal {
5862
enum Localization {
5963
static let barcodeInfoHeading = NSLocalizedString(
6064
"pos.barcodeInfoModal.heading",
6165
value: "Barcode scanning",
6266
comment: "Heading for the barcode info modal in POS, introducing barcode scanning feature"
6367
)
68+
}
69+
}
70+
71+
private extension BarcodeScannerInformationContent {
72+
enum Constants {
73+
static let detailsLink = URL(string: "https://woocommerce.com/document/barcode-and-qr-code-scanner/")
74+
}
75+
76+
enum Localization {
6477
static let barcodeInfoIntroMessage = NSLocalizedString(
6578
"pos.barcodeInfoModal.introMessage",
6679
value: "You can scan barcodes using an external scanner to quickly build a cart.",
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import SwiftUI
2+
3+
// MARK: - Data Models
4+
struct ScannerOption: Identifiable {
5+
let id = UUID()
6+
let title: String
7+
let subtitle: String
8+
let destination: SetupDestination
9+
}
10+
11+
enum SetupDestination {
12+
case socketS720
13+
case starBSH20B
14+
case tbcScanner
15+
case other
16+
}
17+
18+
@available(iOS 17.0, *)
19+
struct PointOfSaleBarcodeScannerSetUpFlow: View {
20+
@Binding var isPresented: Bool
21+
22+
init(isPresented: Binding<Bool>) {
23+
self._isPresented = isPresented
24+
}
25+
26+
var body: some View {
27+
NavigationStack {
28+
VStack(spacing: POSSpacing.xxLarge) {
29+
PointOfSaleModalHeader(isPresented: $isPresented,
30+
title: .constant(AttributedString(Localization.setupHeading)))
31+
32+
VStack {
33+
ScannerSelectionView(options: scannerOptions, isPresented: $isPresented)
34+
Spacer()
35+
}
36+
.scrollVerticallyIfNeeded()
37+
}
38+
.toolbar(.hidden, for: .navigationBar)
39+
.padding(POSPadding.xxLarge)
40+
}
41+
.background(Color.posSurfaceBright)
42+
.containerRelativeFrame([.horizontal, .vertical]) { length, _ in
43+
max(length * 0.75, Constants.modalFrameMaxSmallDimension)
44+
}
45+
.onAppear {
46+
ServiceLocator.analytics.track(.pointOfSaleBarcodeScannerSetupFlowShown)
47+
}
48+
}
49+
50+
private var scannerOptions: [ScannerOption] {
51+
[
52+
ScannerOption(
53+
title: Localization.socketS720Title,
54+
subtitle: Localization.socketS720Subtitle,
55+
destination: .socketS720
56+
),
57+
ScannerOption(
58+
title: Localization.starBSH20BTitle,
59+
subtitle: Localization.starBSH20BSubtitle,
60+
destination: .starBSH20B
61+
),
62+
ScannerOption(
63+
title: Localization.tbcScannerTitle,
64+
subtitle: Localization.tbcScannerSubtitle,
65+
destination: .tbcScanner
66+
),
67+
ScannerOption(
68+
title: Localization.otherTitle,
69+
subtitle: Localization.otherSubtitle,
70+
destination: .other
71+
)
72+
]
73+
}
74+
}
75+
76+
struct ScannerSelectionView: View {
77+
let options: [ScannerOption]
78+
@Binding var isPresented: Bool
79+
80+
var body: some View {
81+
VStack(alignment: .leading, spacing: POSSpacing.medium) {
82+
Text(Localization.setupIntroMessage)
83+
.font(.posBodyLargeRegular())
84+
.foregroundStyle(Color.posOnSurface)
85+
.multilineTextAlignment(.leading)
86+
.fixedSize(horizontal: false, vertical: true)
87+
88+
VStack(spacing: POSSpacing.small) {
89+
ForEach(options) { option in
90+
NavigationLink(destination: destinationView(for: option.destination)) {
91+
ScannerOptionView(
92+
title: option.title,
93+
subtitle: option.subtitle
94+
)
95+
}
96+
.buttonStyle(PlainButtonStyle())
97+
}
98+
}
99+
}
100+
}
101+
102+
@ViewBuilder
103+
private func destinationView(for destination: SetupDestination) -> some View {
104+
switch destination {
105+
case .socketS720:
106+
EmptyView() // TODO: Implement Socket S720 setup flow WOOMOB-698
107+
case .starBSH20B:
108+
EmptyView() // TODO: Implement Star BSH-20B setup flow WOOMOB-696
109+
case .tbcScanner:
110+
EmptyView() // TODO: Implement TBC scanner setup flow WOOMOB-699
111+
case .other:
112+
BarcodeScannerInformationView(isPresented: $isPresented)
113+
.padding(POSPadding.xxLarge)
114+
}
115+
}
116+
}
117+
118+
struct ScannerOptionView: View {
119+
let title: String
120+
let subtitle: String
121+
122+
var body: some View {
123+
HStack {
124+
VStack(alignment: .leading, spacing: POSSpacing.xSmall) {
125+
Text(title)
126+
.font(.posBodyLargeBold)
127+
.foregroundColor(.posOnSurface)
128+
Text(subtitle)
129+
.font(.posBodyMediumRegular())
130+
.foregroundColor(.posOnSurfaceVariantHighest)
131+
}
132+
Spacer()
133+
Image(systemName: "chevron.forward")
134+
.font(.posBodyMediumBold)
135+
.foregroundColor(.posOnSurfaceVariantHighest)
136+
}
137+
.padding(POSPadding.medium)
138+
.background(Color.posSurfaceDim)
139+
.clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.medium.value))
140+
}
141+
}
142+
143+
struct BarcodeScannerInformationView: View {
144+
@Binding var isPresented: Bool
145+
146+
var body: some View {
147+
VStack(spacing: POSSpacing.xxLarge) {
148+
PointOfSaleModalHeader(isPresented: $isPresented,
149+
title: .constant(AttributedString(PointOfSaleBarcodeScannerInformationModal.Localization.barcodeInfoHeading)))
150+
VStack {
151+
BarcodeScannerInformationContent()
152+
Spacer()
153+
}
154+
.scrollVerticallyIfNeeded()
155+
}
156+
.toolbar(.hidden, for: .navigationBar)
157+
}
158+
}
159+
160+
161+
private enum Constants {
162+
static var modalFrameMaxSmallDimension: CGFloat { 752 }
163+
}
164+
165+
// MARK: - Localization
166+
private enum Localization {
167+
static let setupHeading = NSLocalizedString(
168+
"pos.barcodeScannerSetup.heading",
169+
value: "Barcode Scanner Setup",
170+
comment: "Heading for the barcode scanner setup flow in POS"
171+
)
172+
static let setupIntroMessage = NSLocalizedString(
173+
"pos.barcodeScannerSetup.introMessage",
174+
value: "Choose your barcode scanner to get started with the setup process.",
175+
comment: "Introductory message in the barcode scanner setup flow in POS"
176+
)
177+
static let socketS720Title = NSLocalizedString(
178+
"pos.barcodeScannerSetup.socketS720.title",
179+
value: "Socket S720",
180+
comment: "Title for Socket S720 scanner option in barcode scanner setup"
181+
)
182+
static let socketS720Subtitle = NSLocalizedString(
183+
"pos.barcodeScannerSetup.socketS720.subtitle",
184+
value: "Small handheld scanner with a charging dock or stand",
185+
comment: "Subtitle for Socket S720 scanner option in barcode scanner setup"
186+
)
187+
static let starBSH20BTitle = NSLocalizedString(
188+
"pos.barcodeScannerSetup.starBSH20B.title",
189+
value: "Star BSH-20B",
190+
comment: "Title for Star BSH-20B scanner option in barcode scanner setup"
191+
)
192+
static let starBSH20BSubtitle = NSLocalizedString(
193+
"pos.barcodeScannerSetup.starBSH20B.subtitle",
194+
value: "Ergonomic scanner with a stand",
195+
comment: "Subtitle for Star BSH-20B scanner option in barcode scanner setup"
196+
)
197+
static let tbcScannerTitle = NSLocalizedString(
198+
"pos.barcodeScannerSetup.tbcScanner.title",
199+
value: "Scanner TBC",
200+
comment: "Title for TBC scanner option in barcode scanner setup"
201+
)
202+
static let tbcScannerSubtitle = NSLocalizedString(
203+
"pos.barcodeScannerSetup.tbcScanner.subtitle",
204+
value: "Recommended scanner",
205+
comment: "Subtitle for TBC scanner option in barcode scanner setup"
206+
)
207+
static let otherTitle = NSLocalizedString(
208+
"pos.barcodeScannerSetup.other.title",
209+
value: "Other",
210+
comment: "Title for other scanner option in barcode scanner setup"
211+
)
212+
static let otherSubtitle = NSLocalizedString(
213+
"pos.barcodeScannerSetup.other.subtitle",
214+
value: "General scanner setup instructions",
215+
comment: "Subtitle for other scanner option in barcode scanner setup"
216+
)
217+
static let backButtonTitle = NSLocalizedString(
218+
"pos.barcodeScannerSetup.back.button.title",
219+
value: "Back",
220+
comment: "Title for the back button in barcode scanner setup navigation"
221+
)
222+
}
223+
224+
@available(iOS 17.0, *)
225+
#Preview {
226+
PointOfSaleBarcodeScannerSetUpFlow(isPresented: .constant(true))
227+
}

WooCommerce/Classes/POS/Presentation/PointOfSaleInformationModal.swift

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,7 @@ struct PointOfSaleInformationModal<Content: View>: View {
2222

2323
var body: some View {
2424
VStack(spacing: POSSpacing.xxLarge) {
25-
HStack {
26-
Text(title)
27-
.font(.posHeadingBold)
28-
Spacer()
29-
Button {
30-
isPresented = false
31-
} label: {
32-
Text(Image(systemName: "xmark"))
33-
.font(.posButtonSymbolLarge)
34-
}
35-
}
36-
.foregroundColor(Color.posOnSurface)
25+
PointOfSaleModalHeader(isPresented: $isPresented, title: .constant(title))
3726

3827
ScrollView {
3928
VStack {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import SwiftUI
2+
3+
struct PointOfSaleModalHeader: View {
4+
@Binding var isPresented: Bool
5+
@Binding var title: AttributedString
6+
7+
var body: some View {
8+
HStack {
9+
Text(title)
10+
.font(.posHeadingBold)
11+
.lineLimit(1)
12+
Spacer()
13+
Button {
14+
isPresented = false
15+
} label: {
16+
Text(Image(systemName: "xmark"))
17+
.font(.posButtonSymbolLarge)
18+
}
19+
}
20+
.foregroundColor(Color.posOnSurface)
21+
}
22+
}

0 commit comments

Comments
 (0)