Skip to content

Commit 12c61cd

Browse files
authored
[Woo POS][Barcodes] Show barcode scanning information from the menu (#15750)
2 parents 96f2279 + f99c050 commit 12c61cd

File tree

5 files changed

+258
-53
lines changed

5 files changed

+258
-53
lines changed

WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +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
1213

1314
init(showExitPOSModal: Binding<Bool>,
1415
showSupport: Binding<Bool>,
@@ -56,6 +57,15 @@ struct POSFloatingControlView: View {
5657
title: { Text(Localization.productRestrictionsInfo) },
5758
icon: { Image(systemName: "magnifyingglass") })
5859
}
60+
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.pointOfSaleBarcodeScanningi1) {
61+
Button {
62+
showBarcodeScanningInformation = true
63+
} label: {
64+
Label(
65+
title: { Text(Localization.barcodeScanning) },
66+
icon: { Image(systemName: "barcode.viewfinder") })
67+
}
68+
}
5969
} label: {
6070
VStack {
6171
Spacer()
@@ -81,6 +91,9 @@ struct POSFloatingControlView: View {
8191
.posModal(isPresented: $showProductRestrictionsModal) {
8292
SimpleProductsOnlyInformation(isPresented: $showProductRestrictionsModal)
8393
}
94+
.posModal(isPresented: $showBarcodeScanningInformation) {
95+
PointOfSaleBarcodeScannerInformationModal(isPresented: $showBarcodeScanningInformation)
96+
}
8497
.frame(height: Constants.size)
8598
.background(Color.clear)
8699
.animation(.default, value: backgroundAppearance)
@@ -149,6 +162,12 @@ private extension POSFloatingControlView {
149162
comment: "The title of the menu button to view product restrictions info, shown in a popover menu. " +
150163
"We only show simple and variable products in POS, this shows a modal to help explain that limitation."
151164
)
165+
166+
static let barcodeScanning = NSLocalizedString(
167+
"pointOfSale.floatingButtons.barcodeScanning.button.title",
168+
value: "Barcode scanning",
169+
comment: "The title of the menu button to view barcode scanner documentation, shown in a popover menu."
170+
)
152171
}
153172
}
154173

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import Foundation
2+
import SwiftUI
3+
4+
struct PointOfSaleBarcodeScannerInformationModal: View {
5+
@Binding var isPresented: Bool
6+
7+
init(isPresented: Binding<Bool>) {
8+
self._isPresented = isPresented
9+
}
10+
11+
var body: some View {
12+
PointOfSaleInformationModal(isPresented: $isPresented, title: AttributedString(Localization.barcodeInfoHeading)) {
13+
PointOfSaleInformationModalParagraphView {
14+
Text(AttributedString(Localization.barcodeInfoIntroMessage))
15+
}
16+
17+
PointOfSaleInformationModalParagraphView {
18+
Text(bulletPointWithLink)
19+
Text(AttributedString(Localization.barcodeInfoSecondaryMessage))
20+
Text(AttributedString(Localization.barcodeInfoTertiaryMessage))
21+
Text(AttributedString(Localization.barcodeInfoQuaternaryMessage))
22+
}
23+
.padding(.leading, POSSpacing.medium)
24+
25+
PointOfSaleInformationModalParagraphView(style: .outlined) {
26+
Text(AttributedString(Localization.barcodeInfoQuinaryMessage))
27+
}
28+
}
29+
}
30+
31+
private var bulletPointWithLink: AttributedString {
32+
var secondary = AttributedString(Localization.barcodeInfoPrimaryMessage + " ")
33+
var moreDetails = AttributedString(Localization.barcodeInfoMoreDetailsLink)
34+
moreDetails.link = Constants.detailsLink
35+
moreDetails.foregroundColor = .posPrimary
36+
moreDetails.underlineStyle = .single
37+
secondary.append(moreDetails)
38+
return secondary
39+
}
40+
}
41+
42+
private extension PointOfSaleBarcodeScannerInformationModal {
43+
enum Constants {
44+
static let detailsLink = URL(string: "https://woocommerce.com/document/barcode-and-qr-code-scanner/")
45+
}
46+
47+
enum Localization {
48+
static let barcodeInfoHeading = NSLocalizedString(
49+
"pos.barcodeInfoModal.heading",
50+
value: "Barcode scanning",
51+
comment: "Heading for the barcode info modal in POS, introducing barcode scanning feature"
52+
)
53+
static let barcodeInfoIntroMessage = NSLocalizedString(
54+
"pos.barcodeInfoModal.introMessage",
55+
value: "You can scan barcodes using an external scanner to quickly build a cart.",
56+
comment: "Introductory message in the barcode info modal in POS, explaining the use of external barcode scanners"
57+
)
58+
static let barcodeInfoPrimaryMessage = NSLocalizedString(
59+
"pos.barcodeInfoModal.primaryMessage",
60+
value: "• Set up barcodes in the \"GTIN, UPC, EAN, ISBN\" field in Products > Product Details > Inventory. ",
61+
comment: "Primary bullet point in the barcode info modal in POS, instructing where to set up barcodes in product details"
62+
)
63+
static let barcodeInfoMoreDetailsLink = NSLocalizedString(
64+
"pos.barcodeInfoModal.moreDetailsLink",
65+
value: "More details.",
66+
comment: "Link text in the barcode info modal in POS, leading to more details about barcode setup"
67+
)
68+
static let barcodeInfoSecondaryMessage = NSLocalizedString(
69+
"pos.barcodeInfoModal.secondaryMessage",
70+
value: "• Refer to your Bluetooth barcode scanner's instructions to set HID mode.",
71+
comment: "Secondary bullet point in the barcode info modal in POS, instructing to set scanner to HID mode"
72+
)
73+
static let barcodeInfoTertiaryMessage = NSLocalizedString(
74+
"pos.barcodeInfoModal.tertiaryMessage",
75+
value: "• Connect your barcode scanner in iOS Bluetooth settings.",
76+
comment: "Tertiary bullet point in the barcode info modal in POS, instructing to connect scanner via Bluetooth settings"
77+
)
78+
static let barcodeInfoQuaternaryMessage = NSLocalizedString(
79+
"pos.barcodeInfoModal.quaternaryMessage",
80+
value: "• Scan barcodes while on the item list to add products to the cart.",
81+
comment: "Quaternary bullet point in the barcode info modal in POS, instructing to scan barcodes on item list to add to cart"
82+
)
83+
static let barcodeInfoQuinaryMessage = NSLocalizedString(
84+
"pos.barcodeInfoModal.quinaryMessage",
85+
value: "The scanner emulates a keyboard, so sometimes it will prevent the software keyboard from showing, e.g. in search. " +
86+
"Tap on the keyboard icon to show it again.",
87+
comment: "Quinary message in the barcode info modal in POS, explaining scanner keyboard emulation and how to show software keyboard again"
88+
)
89+
}
90+
}
91+
92+
@available(iOS 17.0, *)
93+
#Preview {
94+
PointOfSaleBarcodeScannerInformationModal(isPresented: .constant(true))
95+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import SwiftUI
2+
3+
// Container view for displaying information modals in the POS.
4+
//
5+
struct PointOfSaleInformationModal<Content: View>: View {
6+
@Binding var isPresented: Bool
7+
let title: AttributedString
8+
let content: Content
9+
10+
// Used to make ScrollView height increase together with the content height.
11+
@State private var contentHeight: CGFloat = 0
12+
13+
init(
14+
isPresented: Binding<Bool>,
15+
title: AttributedString,
16+
@ViewBuilder content: () -> Content
17+
) {
18+
self._isPresented = isPresented
19+
self.title = title
20+
self.content = content()
21+
}
22+
23+
var body: some View {
24+
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)
37+
38+
ScrollView {
39+
VStack {
40+
content
41+
}
42+
.measureHeight { height in
43+
// Workaround for ScrollView not updating its height immediately on iOS 17
44+
withAnimation(.easeIn(duration: 0)) {
45+
contentHeight = height
46+
}
47+
}
48+
}
49+
.frame(maxHeight: contentHeight)
50+
51+
Button(action: {
52+
isPresented = false
53+
}) {
54+
Text(Localization.okButtonTitle)
55+
}
56+
.buttonStyle(POSOutlinedButtonStyle(size: .normal))
57+
}
58+
.padding(POSPadding.xxLarge)
59+
.background(Color.posSurfaceBright)
60+
.frame(width: Constants.modalFrameWidth)
61+
}
62+
}
63+
64+
struct PointOfSaleInformationModalParagraphView<Content: View>: View {
65+
enum Style {
66+
case `default`
67+
case outlined
68+
}
69+
70+
let content: Content
71+
let style: Style
72+
73+
init(style: Style = .default, @ViewBuilder content: () -> Content) {
74+
self.content = content()
75+
self.style = style
76+
}
77+
78+
var body: some View {
79+
VStack(alignment: style == .default ? .leading : .center) {
80+
content
81+
}
82+
.if(style == .default, transform: { view in
83+
view.modifier(PointOfSaleInformationModalDefaultParagraphStyle())
84+
})
85+
.if(style == .outlined, transform: { view in
86+
view.modifier(PointOfSaleInformationModalOutlinedParagraphStyle())
87+
})
88+
}
89+
}
90+
91+
private struct PointOfSaleInformationModalDefaultParagraphStyle: ViewModifier {
92+
func body(content: Content) -> some View {
93+
content
94+
.frame(maxWidth: .infinity, alignment: .leading)
95+
.font(.posBodyLargeRegular())
96+
.foregroundStyle(Color.posOnSurface)
97+
.multilineTextAlignment(.leading)
98+
.fixedSize(horizontal: false, vertical: true)
99+
}
100+
}
101+
102+
private struct PointOfSaleInformationModalOutlinedParagraphStyle: ViewModifier {
103+
func body(content: Content) -> some View {
104+
content
105+
.frame(maxWidth: .infinity, alignment: .center)
106+
.font(.posBodySmallRegular())
107+
.foregroundStyle(Color.posOnSurface)
108+
.padding(POSPadding.medium)
109+
.background(Color.posSurfaceDim)
110+
.clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.medium.value))
111+
.multilineTextAlignment(.center)
112+
.fixedSize(horizontal: false, vertical: true)
113+
}
114+
}
115+
116+
private extension PointOfSaleInformationModal {
117+
enum Constants {
118+
static var modalFrameWidth: CGFloat { 896 }
119+
}
120+
}
121+
122+
private enum Localization {
123+
static let okButtonTitle = NSLocalizedString(
124+
"pos.posInformationModal.ok.button.title",
125+
value: "OK",
126+
comment: "Title for the OK button on the pos information modal"
127+
)
128+
}

WooCommerce/Classes/POS/Presentation/SimpleProductsOnlyInformation.swift

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,60 +12,26 @@ struct SimpleProductsOnlyInformation: View {
1212
}
1313

1414
var body: some View {
15-
VStack(spacing: Constants.contentBlockSpacing) {
16-
HStack {
17-
Spacer()
18-
Button {
19-
isPresented = false
20-
} label: {
21-
Text(Image(systemName: "xmark"))
22-
.font(.posButtonSymbolLarge)
23-
}
24-
.padding(Constants.dismissIconPadding)
25-
.foregroundColor(Color.posOnSurfaceVariantLowest)
26-
}
27-
28-
VStack(spacing: Constants.textSpacing) {
29-
Text(Localization.modalTitle)
30-
.font(.posHeadingBold)
31-
32-
Group {
33-
Text(issueMessage)
34-
Text(futureMessage)
35-
}
36-
.font(.posBodyLargeRegular())
15+
PointOfSaleInformationModal(isPresented: $isPresented, title: AttributedString(Localization.modalTitle)) {
16+
PointOfSaleInformationModalParagraphView {
17+
Text(issueMessage)
18+
Text(futureMessage)
3719
}
38-
.foregroundStyle(Color.posOnSurface)
39-
.multilineTextAlignment(.center)
4020

41-
VStack(spacing: Constants.textSpacing) {
21+
PointOfSaleInformationModalParagraphView(style: .outlined) {
4222
Text(hintMessage)
43-
.font(.posBodySmallRegular())
44-
.foregroundStyle(Color.posOnSurface)
23+
24+
Spacer().frame(height: POSSpacing.small)
4525

4626
Button {
4727
deepLinkNavigator?.navigate(to: OrdersDestination.createOrder)
4828
} label: {
4929
Label(Localization.modalAction, systemImage: "plus")
5030
.font(.posBodySmallRegular())
5131
}
32+
.foregroundStyle(Color.posPrimary)
5233
}
53-
.frame(maxWidth: .infinity)
54-
.padding(.vertical, Constants.hintVerticalPadding)
55-
.padding(.horizontal, Constants.hintHorizontalPadding)
56-
.background(Color(.posSurfaceDim))
57-
.clipShape(RoundedRectangle(cornerRadius: Constants.hintBackgroundCornerRadius))
58-
.multilineTextAlignment(.center)
59-
60-
Button(action: {
61-
isPresented = false
62-
}) {
63-
Text(Localization.okButtonTitle)
64-
}
65-
.buttonStyle(POSOutlinedButtonStyle(size: .normal))
6634
}
67-
.padding(Constants.modalContentPadding)
68-
.frame(width: Constants.modalFrameWidth)
6935
}
7036

7137
private var issueMessage: String {
@@ -84,17 +50,6 @@ struct SimpleProductsOnlyInformation: View {
8450
// Constants and Localization enums
8551
@available(iOS 17.0, *)
8652
private extension SimpleProductsOnlyInformation {
87-
enum Constants {
88-
static let modalFrameWidth: CGFloat = 896
89-
static let modalContentPadding: CGFloat = POSSpacing.medium
90-
static let hintVerticalPadding: CGFloat = POSSpacing.medium
91-
static let hintHorizontalPadding: CGFloat = POSSpacing.medium
92-
static let hintBackgroundCornerRadius: CGFloat = POSCornerRadiusStyle.medium.value
93-
static let contentBlockSpacing: CGFloat = POSSpacing.xxLarge
94-
static let textSpacing: CGFloat = POSSpacing.small
95-
static let dismissIconPadding: EdgeInsets = .init(top: 8, leading: 8, bottom: 8, trailing: 8)
96-
}
97-
9853
enum Localization {
9954
static let modalTitle = NSLocalizedString(
10055
"pos.simpleProductsModal.title",

0 commit comments

Comments
 (0)