From 770ec65ed4e8047396af19377d45923ef67b1239 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 8 Jul 2025 15:41:55 +0100 Subject: [PATCH 01/21] Initial flow-based setup implementation --- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 302 +++++++++++++++--- 1 file changed, 257 insertions(+), 45 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift index 18270ca10cd..5e21a0691ad 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift @@ -5,39 +5,137 @@ struct ScannerOption: Identifiable { let id = UUID() let title: String let subtitle: String - let destination: SetupDestination + let scannerType: ScannerType } -enum SetupDestination { +enum ScannerType { case socketS720 case starBSH20B case tbcScanner case other } +// MARK: - Flow State +enum SetupFlowState { + case scannerSelection + case setupFlow(ScannerType) +} + +// MARK: - Setup Flow Manager +class SetupFlowManager: ObservableObject { + @Published var currentState: SetupFlowState = .scannerSelection + + func selectScanner(_ scannerType: ScannerType) { + currentState = .setupFlow(scannerType) + } + + func goBackToSelection() { + currentState = .scannerSelection + } + + func getCurrentStep() -> SetupStep? { + switch currentState { + case .scannerSelection: + return nil + case .setupFlow(let scannerType): + return getStep(for: scannerType) + } + } + + private func getStep(for scannerType: ScannerType) -> SetupStep { + switch scannerType { + case .socketS720: + return SetupStep( + title: "Socket S720 Setup", + content: { SocketS720WelcomeView() }, + nextButtonTitle: "Done", + canGoBack: true + ) + case .starBSH20B: + return SetupStep( + title: "Star BSH-20B Setup", + content: { StarBSH20BWelcomeView() }, + nextButtonTitle: "Done", + canGoBack: true + ) + case .tbcScanner: + return SetupStep( + title: "TBC Scanner Setup", + content: { TBCScannerWelcomeView() }, + nextButtonTitle: "Done", + canGoBack: true + ) + case .other: + return SetupStep( + title: "General Scanner Setup", + content: { OtherScannerView() }, + nextButtonTitle: "Done", + canGoBack: true + ) + } + } +} + +// MARK: - Setup Step +struct SetupStep { + let title: String + let content: any View + let nextButtonTitle: String + let isNextButtonEnabled: Bool + let canGoBack: Bool + + init( + title: String, + @ViewBuilder content: () -> any View, + nextButtonTitle: String = "Next", + isNextButtonEnabled: Bool = true, + canGoBack: Bool = true + ) { + self.title = title + self.content = content() + self.nextButtonTitle = nextButtonTitle + self.isNextButtonEnabled = isNextButtonEnabled + self.canGoBack = canGoBack + } +} + @available(iOS 17.0, *) struct PointOfSaleBarcodeScannerSetUpFlow: View { @Binding var isPresented: Bool + @StateObject private var flowManager = SetupFlowManager() init(isPresented: Binding) { self._isPresented = isPresented } var body: some View { - NavigationStack { - VStack(spacing: POSSpacing.xxLarge) { - PointOfSaleModalHeader(isPresented: $isPresented, - title: .constant(AttributedString(Localization.setupHeading))) - - VStack { - ScannerSelectionView(options: scannerOptions, isPresented: $isPresented) - Spacer() + VStack(spacing: POSSpacing.xxLarge) { + // Header + PointOfSaleModalHeader(isPresented: $isPresented, + title: .constant(AttributedString(currentTitle))) + + VStack { + currentContent + Spacer() + } + .scrollVerticallyIfNeeded() + + // Bottom buttons + HStack(spacing: POSSpacing.medium) { + Button(Localization.backButtonTitle) { + handleBackButton() } - .scrollVerticallyIfNeeded() + .buttonStyle(POSOutlinedButtonStyle(size: .normal)) + .renderedIf(shouldShowBackButton) + + Button(currentNextButtonTitle) { + handleNextButton() + } + .buttonStyle(POSFilledButtonStyle(size: .normal)) + .disabled(!isNextButtonEnabled) } - .toolbar(.hidden, for: .navigationBar) - .padding(POSPadding.xxLarge) } + .padding(POSPadding.xxLarge) .background(Color.posSurfaceBright) .containerRelativeFrame([.horizontal, .vertical]) { length, _ in max(length * 0.75, Constants.modalFrameMaxSmallDimension) @@ -47,35 +145,106 @@ struct PointOfSaleBarcodeScannerSetUpFlow: View { } } + // MARK: - Computed Properties + private var currentTitle: String { + switch flowManager.currentState { + case .scannerSelection: + return Localization.setupHeading + case .setupFlow: + return flowManager.getCurrentStep()?.title ?? Localization.setupHeading + } + } + + @ViewBuilder + private var currentContent: some View { + switch flowManager.currentState { + case .scannerSelection: + ScannerSelectionView(options: scannerOptions) { scannerType in + flowManager.selectScanner(scannerType) + } + case .setupFlow: + if let step = flowManager.getCurrentStep() { + AnyView(step.content) + } + } + } + + private var currentNextButtonTitle: String { + switch flowManager.currentState { + case .scannerSelection: + return Localization.selectButtonTitle + case .setupFlow: + return flowManager.getCurrentStep()?.nextButtonTitle ?? Localization.nextButtonTitle + } + } + + private var isNextButtonEnabled: Bool { + switch flowManager.currentState { + case .scannerSelection: + return false // No selection made yet + case .setupFlow: + return flowManager.getCurrentStep()?.isNextButtonEnabled ?? false + } + } + + private var shouldShowBackButton: Bool { + switch flowManager.currentState { + case .scannerSelection: + return false + case .setupFlow: + return flowManager.getCurrentStep()?.canGoBack ?? false + } + } + + // MARK: - Actions + private func handleBackButton() { + switch flowManager.currentState { + case .scannerSelection: + break // Should not happen + case .setupFlow: + flowManager.goBackToSelection() + } + } + + private func handleNextButton() { + switch flowManager.currentState { + case .scannerSelection: + break // Should not happen + case .setupFlow: + isPresented = false + } + } + private var scannerOptions: [ScannerOption] { [ ScannerOption( title: Localization.socketS720Title, subtitle: Localization.socketS720Subtitle, - destination: .socketS720 + scannerType: .socketS720 ), ScannerOption( title: Localization.starBSH20BTitle, subtitle: Localization.starBSH20BSubtitle, - destination: .starBSH20B + scannerType: .starBSH20B ), ScannerOption( title: Localization.tbcScannerTitle, subtitle: Localization.tbcScannerSubtitle, - destination: .tbcScanner + scannerType: .tbcScanner ), ScannerOption( title: Localization.otherTitle, subtitle: Localization.otherSubtitle, - destination: .other + scannerType: .other ) ] } } +// MARK: - Scanner Selection View struct ScannerSelectionView: View { let options: [ScannerOption] - @Binding var isPresented: Bool + let onSelection: (ScannerType) -> Void var body: some View { VStack(alignment: .leading, spacing: POSSpacing.medium) { @@ -87,7 +256,9 @@ struct ScannerSelectionView: View { VStack(spacing: POSSpacing.small) { ForEach(options) { option in - NavigationLink(destination: destinationView(for: option.destination)) { + Button { + onSelection(option.scannerType) + } label: { ScannerOptionView( title: option.title, subtitle: option.subtitle @@ -98,23 +269,9 @@ struct ScannerSelectionView: View { } } } - - @ViewBuilder - private func destinationView(for destination: SetupDestination) -> some View { - switch destination { - case .socketS720: - EmptyView() // TODO: Implement Socket S720 setup flow WOOMOB-698 - case .starBSH20B: - EmptyView() // TODO: Implement Star BSH-20B setup flow WOOMOB-696 - case .tbcScanner: - EmptyView() // TODO: Implement TBC scanner setup flow WOOMOB-699 - case .other: - BarcodeScannerInformationView(isPresented: $isPresented) - .padding(POSPadding.xxLarge) - } - } } +// MARK: - Scanner Option View struct ScannerOptionView: View { let title: String let subtitle: String @@ -140,24 +297,64 @@ struct ScannerOptionView: View { } } -struct BarcodeScannerInformationView: View { - @Binding var isPresented: Bool +// MARK: - Step Views +struct SocketS720WelcomeView: View { + var body: some View { + VStack(spacing: POSSpacing.medium) { + Text("Socket S720 Scanner") + .font(.posBodyLargeBold) + .foregroundColor(.posOnSurface) + Text("TODO: Implement Socket S720 setup flow WOOMOB-698") + .font(.posBodyMediumRegular()) + .foregroundColor(.posOnSurfaceVariantHighest) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +struct StarBSH20BWelcomeView: View { var body: some View { - VStack(spacing: POSSpacing.xxLarge) { - PointOfSaleModalHeader(isPresented: $isPresented, - title: .constant(AttributedString(PointOfSaleBarcodeScannerInformationModal.Localization.barcodeInfoHeading))) - VStack { - BarcodeScannerInformationContent() - Spacer() - } - .scrollVerticallyIfNeeded() + VStack(spacing: POSSpacing.medium) { + Text("Star BSH-20B Scanner") + .font(.posBodyLargeBold) + .foregroundColor(.posOnSurface) + + Text("TODO: Implement Star BSH-20B setup flow WOOMOB-696") + .font(.posBodyMediumRegular()) + .foregroundColor(.posOnSurfaceVariantHighest) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +struct TBCScannerWelcomeView: View { + var body: some View { + VStack(spacing: POSSpacing.medium) { + Text("TBC Scanner") + .font(.posBodyLargeBold) + .foregroundColor(.posOnSurface) + + Text("TODO: Implement TBC scanner setup flow WOOMOB-699") + .font(.posBodyMediumRegular()) + .foregroundColor(.posOnSurfaceVariantHighest) + .multilineTextAlignment(.center) } - .toolbar(.hidden, for: .navigationBar) + .frame(maxWidth: .infinity, maxHeight: .infinity) } } +struct OtherScannerView: View { + var body: some View { + VStack(spacing: POSSpacing.medium) { + BarcodeScannerInformationContent() + } + } +} +// MARK: - Constants private enum Constants { static var modalFrameMaxSmallDimension: CGFloat { 752 } } @@ -219,6 +416,21 @@ private enum Localization { value: "Back", comment: "Title for the back button in barcode scanner setup navigation" ) + static let nextButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.next.button.title", + value: "Next", + comment: "Title for the next button in barcode scanner setup navigation" + ) + static let doneButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.done.button.title", + value: "Done", + comment: "Title for the done button in barcode scanner setup navigation" + ) + static let selectButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.select.button.title", + value: "Select", + comment: "Title for the select button in barcode scanner setup navigation" + ) } @available(iOS 17.0, *) From 58b363d81006dc9fc216b19800bb2ce8a9d4a90b Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 8 Jul 2025 16:07:48 +0100 Subject: [PATCH 02/21] Simplify the flow specification approach --- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 202 +++++++++--------- 1 file changed, 105 insertions(+), 97 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift index 5e21a0691ad..3432a691e80 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift @@ -21,9 +21,55 @@ enum SetupFlowState { case setupFlow(ScannerType) } +// MARK: - Setup Step +struct SetupStep { + let title: String + let content: any View + let nextButtonTitle: String + let isNextButtonEnabled: Bool + let canGoBack: Bool + let onNext: () -> Void + let onBack: () -> Void + + init( + title: String, + @ViewBuilder content: () -> any View, + nextButtonTitle: String = Localization.nextButtonTitle, + isNextButtonEnabled: Bool = true, + canGoBack: Bool = true, + onNext: @escaping () -> Void, + onBack: @escaping () -> Void + ) { + self.title = title + self.content = content() + self.nextButtonTitle = nextButtonTitle + self.isNextButtonEnabled = isNextButtonEnabled + self.canGoBack = canGoBack + self.onNext = onNext + self.onBack = onBack + } +} + +extension SetupStep { + var shouldShowBackButton: Bool { + canGoBack + } + + var shouldShowNextButton: Bool { + true // Always show next button + } +} + // MARK: - Setup Flow Manager -class SetupFlowManager: ObservableObject { - @Published var currentState: SetupFlowState = .scannerSelection +@available(iOS 17.0, *) +@Observable +class SetupFlowManager { + var currentState: SetupFlowState = .scannerSelection + @ObservationIgnored @Binding var isPresented: Bool + + init(isPresented: Binding) { + self._isPresented = isPresented + } func selectScanner(_ scannerType: ScannerType) { currentState = .setupFlow(scannerType) @@ -48,64 +94,66 @@ class SetupFlowManager: ObservableObject { return SetupStep( title: "Socket S720 Setup", content: { SocketS720WelcomeView() }, - nextButtonTitle: "Done", - canGoBack: true + nextButtonTitle: Localization.doneButtonTitle, + canGoBack: true, + onNext: { [weak self] in + self?.isPresented = false + }, + onBack: { [weak self] in + self?.goBackToSelection() + } ) case .starBSH20B: return SetupStep( title: "Star BSH-20B Setup", content: { StarBSH20BWelcomeView() }, - nextButtonTitle: "Done", - canGoBack: true + nextButtonTitle: Localization.doneButtonTitle, + canGoBack: true, + onNext: { [weak self] in + self?.isPresented = false + }, + onBack: { [weak self] in + self?.goBackToSelection() + } ) case .tbcScanner: return SetupStep( title: "TBC Scanner Setup", content: { TBCScannerWelcomeView() }, - nextButtonTitle: "Done", - canGoBack: true + nextButtonTitle: Localization.doneButtonTitle, + canGoBack: true, + onNext: { [weak self] in + self?.isPresented = false + }, + onBack: { [weak self] in + self?.goBackToSelection() + } ) case .other: return SetupStep( title: "General Scanner Setup", content: { OtherScannerView() }, nextButtonTitle: "Done", - canGoBack: true + canGoBack: true, + onNext: { [weak self] in + self?.isPresented = false + }, + onBack: { [weak self] in + self?.goBackToSelection() + } ) } } } -// MARK: - Setup Step -struct SetupStep { - let title: String - let content: any View - let nextButtonTitle: String - let isNextButtonEnabled: Bool - let canGoBack: Bool - - init( - title: String, - @ViewBuilder content: () -> any View, - nextButtonTitle: String = "Next", - isNextButtonEnabled: Bool = true, - canGoBack: Bool = true - ) { - self.title = title - self.content = content() - self.nextButtonTitle = nextButtonTitle - self.isNextButtonEnabled = isNextButtonEnabled - self.canGoBack = canGoBack - } -} - @available(iOS 17.0, *) struct PointOfSaleBarcodeScannerSetUpFlow: View { @Binding var isPresented: Bool - @StateObject private var flowManager = SetupFlowManager() + @State private var flowManager: SetupFlowManager init(isPresented: Binding) { self._isPresented = isPresented + self.flowManager = SetupFlowManager(isPresented: isPresented) } var body: some View { @@ -121,19 +169,7 @@ struct PointOfSaleBarcodeScannerSetUpFlow: View { .scrollVerticallyIfNeeded() // Bottom buttons - HStack(spacing: POSSpacing.medium) { - Button(Localization.backButtonTitle) { - handleBackButton() - } - .buttonStyle(POSOutlinedButtonStyle(size: .normal)) - .renderedIf(shouldShowBackButton) - - Button(currentNextButtonTitle) { - handleNextButton() - } - .buttonStyle(POSFilledButtonStyle(size: .normal)) - .disabled(!isNextButtonEnabled) - } + FlowButtonsView(flowManager: flowManager) } .padding(POSPadding.xxLarge) .background(Color.posSurfaceBright) @@ -169,52 +205,6 @@ struct PointOfSaleBarcodeScannerSetUpFlow: View { } } - private var currentNextButtonTitle: String { - switch flowManager.currentState { - case .scannerSelection: - return Localization.selectButtonTitle - case .setupFlow: - return flowManager.getCurrentStep()?.nextButtonTitle ?? Localization.nextButtonTitle - } - } - - private var isNextButtonEnabled: Bool { - switch flowManager.currentState { - case .scannerSelection: - return false // No selection made yet - case .setupFlow: - return flowManager.getCurrentStep()?.isNextButtonEnabled ?? false - } - } - - private var shouldShowBackButton: Bool { - switch flowManager.currentState { - case .scannerSelection: - return false - case .setupFlow: - return flowManager.getCurrentStep()?.canGoBack ?? false - } - } - - // MARK: - Actions - private func handleBackButton() { - switch flowManager.currentState { - case .scannerSelection: - break // Should not happen - case .setupFlow: - flowManager.goBackToSelection() - } - } - - private func handleNextButton() { - switch flowManager.currentState { - case .scannerSelection: - break // Should not happen - case .setupFlow: - isPresented = false - } - } - private var scannerOptions: [ScannerOption] { [ ScannerOption( @@ -241,6 +231,29 @@ struct PointOfSaleBarcodeScannerSetUpFlow: View { } } +// MARK: - Flow Buttons View +@available(iOS 17.0, *) +struct FlowButtonsView: View { + let flowManager: SetupFlowManager + + var body: some View { + HStack(spacing: POSSpacing.medium) { + if let step = flowManager.getCurrentStep(), step.shouldShowBackButton { + Button(Localization.backButtonTitle) { + step.onBack() + } + .buttonStyle(POSOutlinedButtonStyle(size: .normal)) + + Button(step.nextButtonTitle) { + step.onNext() + } + .buttonStyle(POSFilledButtonStyle(size: .normal)) + .disabled(!step.isNextButtonEnabled) + } + } + } +} + // MARK: - Scanner Selection View struct ScannerSelectionView: View { let options: [ScannerOption] @@ -426,11 +439,6 @@ private enum Localization { value: "Done", comment: "Title for the done button in barcode scanner setup navigation" ) - static let selectButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.select.button.title", - value: "Select", - comment: "Title for the select button in barcode scanner setup navigation" - ) } @available(iOS 17.0, *) From 548e4fbe27e28c011cddb8571ed42ce8d659c8e7 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 8 Jul 2025 16:18:01 +0100 Subject: [PATCH 03/21] Reduce temporary overhead in the code --- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 181 +++++++++--------- 1 file changed, 86 insertions(+), 95 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift index 3432a691e80..152e5f6388c 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift @@ -18,7 +18,7 @@ enum ScannerType { // MARK: - Flow State enum SetupFlowState { case scannerSelection - case setupFlow(ScannerType) + case setupFlow(ScannerType, stepIndex: Int = 0) } // MARK: - Setup Step @@ -72,76 +72,105 @@ class SetupFlowManager { } func selectScanner(_ scannerType: ScannerType) { - currentState = .setupFlow(scannerType) + currentState = .setupFlow(scannerType, stepIndex: 0) } func goBackToSelection() { currentState = .scannerSelection } + func nextStep() { + switch currentState { + case .scannerSelection: + break + case .setupFlow(let scannerType, let stepIndex): + let steps = getSteps(for: scannerType) + if stepIndex < steps.count - 1 { + currentState = .setupFlow(scannerType, stepIndex: stepIndex + 1) + } + } + } + + func previousStep() { + switch currentState { + case .scannerSelection: + break + case .setupFlow(let scannerType, let stepIndex): + if stepIndex > 0 { + currentState = .setupFlow(scannerType, stepIndex: stepIndex - 1) + } else { + goBackToSelection() + } + } + } + func getCurrentStep() -> SetupStep? { switch currentState { case .scannerSelection: return nil - case .setupFlow(let scannerType): - return getStep(for: scannerType) + case .setupFlow(let scannerType, let stepIndex): + let steps = getSteps(for: scannerType) + return steps[safe: stepIndex] } } - private func getStep(for scannerType: ScannerType) -> SetupStep { + func isComplete() -> Bool { + switch currentState { + case .scannerSelection: + return false + case .setupFlow(let scannerType, let stepIndex): + let steps = getSteps(for: scannerType) + return stepIndex >= steps.count - 1 + } + } + + private func createWelcomeStep(title: String) -> SetupStep { + SetupStep( + title: title, + content: { ScannerWelcomeView(title: title) }, + nextButtonTitle: Localization.doneButtonTitle, + canGoBack: true, + onNext: { [weak self] in + self?.isPresented = false + }, + onBack: { [weak self] in + self?.previousStep() + } + ) + } + + private func getSteps(for scannerType: ScannerType) -> [SetupStep] { switch scannerType { case .socketS720: - return SetupStep( - title: "Socket S720 Setup", - content: { SocketS720WelcomeView() }, - nextButtonTitle: Localization.doneButtonTitle, - canGoBack: true, - onNext: { [weak self] in - self?.isPresented = false - }, - onBack: { [weak self] in - self?.goBackToSelection() - } - ) + return [ + createWelcomeStep(title: "Socket S720 Setup") + // TODO: Add more steps for Socket S720 WOOMOB-698 + ] case .starBSH20B: - return SetupStep( - title: "Star BSH-20B Setup", - content: { StarBSH20BWelcomeView() }, - nextButtonTitle: Localization.doneButtonTitle, - canGoBack: true, - onNext: { [weak self] in - self?.isPresented = false - }, - onBack: { [weak self] in - self?.goBackToSelection() - } - ) + return [ + createWelcomeStep(title: "Star BSH-20B Setup") + // TODO: Add more steps for Star BSH-20B WOOMOB-696 + ] case .tbcScanner: - return SetupStep( - title: "TBC Scanner Setup", - content: { TBCScannerWelcomeView() }, - nextButtonTitle: Localization.doneButtonTitle, - canGoBack: true, - onNext: { [weak self] in - self?.isPresented = false - }, - onBack: { [weak self] in - self?.goBackToSelection() - } - ) + return [ + createWelcomeStep(title: "TBC Scanner Setup") + // TODO: Add more steps for TBC Scanner WOOMOB-699 + ] case .other: - return SetupStep( - title: "General Scanner Setup", - content: { OtherScannerView() }, - nextButtonTitle: "Done", - canGoBack: true, - onNext: { [weak self] in - self?.isPresented = false - }, - onBack: { [weak self] in - self?.goBackToSelection() - } - ) + return [ + SetupStep( + title: "General Scanner Setup", + content: { BarcodeScannerInformationContent() }, + nextButtonTitle: Localization.doneButtonTitle, + canGoBack: true, + onNext: { [weak self] in + self?.isPresented = false + }, + onBack: { [weak self] in + self?.previousStep() + } + ) + ] } } } @@ -311,46 +340,16 @@ struct ScannerOptionView: View { } // MARK: - Step Views -struct SocketS720WelcomeView: View { - var body: some View { - VStack(spacing: POSSpacing.medium) { - Text("Socket S720 Scanner") - .font(.posBodyLargeBold) - .foregroundColor(.posOnSurface) - - Text("TODO: Implement Socket S720 setup flow WOOMOB-698") - .font(.posBodyMediumRegular()) - .foregroundColor(.posOnSurfaceVariantHighest) - .multilineTextAlignment(.center) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} - -struct StarBSH20BWelcomeView: View { - var body: some View { - VStack(spacing: POSSpacing.medium) { - Text("Star BSH-20B Scanner") - .font(.posBodyLargeBold) - .foregroundColor(.posOnSurface) - - Text("TODO: Implement Star BSH-20B setup flow WOOMOB-696") - .font(.posBodyMediumRegular()) - .foregroundColor(.posOnSurfaceVariantHighest) - .multilineTextAlignment(.center) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} +struct ScannerWelcomeView: View { + let title: String -struct TBCScannerWelcomeView: View { var body: some View { VStack(spacing: POSSpacing.medium) { - Text("TBC Scanner") + Text(title) .font(.posBodyLargeBold) .foregroundColor(.posOnSurface) - Text("TODO: Implement TBC scanner setup flow WOOMOB-699") + Text("TODO: Implement \(title) setup flow") .font(.posBodyMediumRegular()) .foregroundColor(.posOnSurfaceVariantHighest) .multilineTextAlignment(.center) @@ -359,14 +358,6 @@ struct TBCScannerWelcomeView: View { } } -struct OtherScannerView: View { - var body: some View { - VStack(spacing: POSSpacing.medium) { - BarcodeScannerInformationContent() - } - } -} - // MARK: - Constants private enum Constants { static var modalFrameMaxSmallDimension: CGFloat { 752 } From 2c1b3c77d90c5b7f4a54bb9ce4e8d4e06c72176c Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 8 Jul 2025 16:22:02 +0100 Subject: [PATCH 04/21] Guard case for step finding functions --- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift index 152e5f6388c..46a969670e0 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift @@ -80,48 +80,32 @@ class SetupFlowManager { } func nextStep() { - switch currentState { - case .scannerSelection: - break - case .setupFlow(let scannerType, let stepIndex): - let steps = getSteps(for: scannerType) - if stepIndex < steps.count - 1 { - currentState = .setupFlow(scannerType, stepIndex: stepIndex + 1) - } + guard case .setupFlow(let scannerType, let stepIndex) = currentState else { return } + let steps = getSteps(for: scannerType) + if stepIndex < steps.count - 1 { + currentState = .setupFlow(scannerType, stepIndex: stepIndex + 1) } } func previousStep() { - switch currentState { - case .scannerSelection: - break - case .setupFlow(let scannerType, let stepIndex): - if stepIndex > 0 { - currentState = .setupFlow(scannerType, stepIndex: stepIndex - 1) - } else { - goBackToSelection() - } + guard case .setupFlow(let scannerType, let stepIndex) = currentState else { return } + if stepIndex > 0 { + currentState = .setupFlow(scannerType, stepIndex: stepIndex - 1) + } else { + goBackToSelection() } } func getCurrentStep() -> SetupStep? { - switch currentState { - case .scannerSelection: - return nil - case .setupFlow(let scannerType, let stepIndex): - let steps = getSteps(for: scannerType) - return steps[safe: stepIndex] - } + guard case .setupFlow(let scannerType, let stepIndex) = currentState else { return nil } + let steps = getSteps(for: scannerType) + return steps[safe: stepIndex] } func isComplete() -> Bool { - switch currentState { - case .scannerSelection: - return false - case .setupFlow(let scannerType, let stepIndex): - let steps = getSteps(for: scannerType) - return stepIndex >= steps.count - 1 - } + guard case .setupFlow(let scannerType, let stepIndex) = currentState else { return false } + let steps = getSteps(for: scannerType) + return stepIndex >= steps.count - 1 } private func createWelcomeStep(title: String) -> SetupStep { From 512b836e5ff4cd957e775b6d082d9ebda5d039c5 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Tue, 8 Jul 2025 16:28:39 +0100 Subject: [PATCH 05/21] Move step management out of overall flow manager --- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 131 +++++++++++------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift index 46a969670e0..9be6a390049 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift @@ -18,7 +18,7 @@ enum ScannerType { // MARK: - Flow State enum SetupFlowState { case scannerSelection - case setupFlow(ScannerType, stepIndex: Int = 0) + case setupFlow(ScannerType) } // MARK: - Setup Step @@ -60,70 +60,43 @@ extension SetupStep { } } -// MARK: - Setup Flow Manager +// MARK: - Scanner Flow @available(iOS 17.0, *) -@Observable -class SetupFlowManager { - var currentState: SetupFlowState = .scannerSelection - @ObservationIgnored @Binding var isPresented: Bool - - init(isPresented: Binding) { - self._isPresented = isPresented +class ScannerFlow { + private let scannerType: ScannerType + private let onComplete: () -> Void + private let onBackToSelection: () -> Void + private var currentStepIndex: Int = 0 + + init(scannerType: ScannerType, onComplete: @escaping () -> Void, onBackToSelection: @escaping () -> Void) { + self.scannerType = scannerType + self.onComplete = onComplete + self.onBackToSelection = onBackToSelection } - func selectScanner(_ scannerType: ScannerType) { - currentState = .setupFlow(scannerType, stepIndex: 0) + var currentStep: SetupStep? { + steps[safe: currentStepIndex] } - func goBackToSelection() { - currentState = .scannerSelection + var isComplete: Bool { + currentStepIndex >= steps.count - 1 } func nextStep() { - guard case .setupFlow(let scannerType, let stepIndex) = currentState else { return } - let steps = getSteps(for: scannerType) - if stepIndex < steps.count - 1 { - currentState = .setupFlow(scannerType, stepIndex: stepIndex + 1) + if currentStepIndex < steps.count - 1 { + currentStepIndex += 1 } } func previousStep() { - guard case .setupFlow(let scannerType, let stepIndex) = currentState else { return } - if stepIndex > 0 { - currentState = .setupFlow(scannerType, stepIndex: stepIndex - 1) + if currentStepIndex > 0 { + currentStepIndex -= 1 } else { - goBackToSelection() + onBackToSelection() } } - func getCurrentStep() -> SetupStep? { - guard case .setupFlow(let scannerType, let stepIndex) = currentState else { return nil } - let steps = getSteps(for: scannerType) - return steps[safe: stepIndex] - } - - func isComplete() -> Bool { - guard case .setupFlow(let scannerType, let stepIndex) = currentState else { return false } - let steps = getSteps(for: scannerType) - return stepIndex >= steps.count - 1 - } - - private func createWelcomeStep(title: String) -> SetupStep { - SetupStep( - title: title, - content: { ScannerWelcomeView(title: title) }, - nextButtonTitle: Localization.doneButtonTitle, - canGoBack: true, - onNext: { [weak self] in - self?.isPresented = false - }, - onBack: { [weak self] in - self?.previousStep() - } - ) - } - - private func getSteps(for scannerType: ScannerType) -> [SetupStep] { + private var steps: [SetupStep] { switch scannerType { case .socketS720: return [ @@ -147,9 +120,7 @@ class SetupFlowManager { content: { BarcodeScannerInformationContent() }, nextButtonTitle: Localization.doneButtonTitle, canGoBack: true, - onNext: { [weak self] in - self?.isPresented = false - }, + onNext: onComplete, onBack: { [weak self] in self?.previousStep() } @@ -157,6 +128,62 @@ class SetupFlowManager { ] } } + + private func createWelcomeStep(title: String) -> SetupStep { + SetupStep( + title: title, + content: { ScannerWelcomeView(title: title) }, + nextButtonTitle: Localization.doneButtonTitle, + canGoBack: true, + onNext: onComplete, + onBack: { [weak self] in + self?.previousStep() + } + ) + } +} + +// MARK: - Setup Flow Manager +@available(iOS 17.0, *) +@Observable +class SetupFlowManager { + var currentState: SetupFlowState = .scannerSelection + @ObservationIgnored @Binding var isPresented: Bool + private var currentFlow: ScannerFlow? + + init(isPresented: Binding) { + self._isPresented = isPresented + } + + func selectScanner(_ scannerType: ScannerType) { + currentFlow = ScannerFlow(scannerType: scannerType, onComplete: { [weak self] in + self?.isPresented = false + }, onBackToSelection: { [weak self] in + self?.goBackToSelection() + }) + currentState = .setupFlow(scannerType) + } + + func goBackToSelection() { + currentState = .scannerSelection + currentFlow = nil + } + + func nextStep() { + currentFlow?.nextStep() + } + + func previousStep() { + currentFlow?.previousStep() + } + + func getCurrentStep() -> SetupStep? { + currentFlow?.currentStep + } + + func isComplete() -> Bool { + currentFlow?.isComplete ?? false + } } @available(iOS 17.0, *) From ccecc97522a767465fc43545f7efc6782ea0387b Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 09:07:28 +0100 Subject: [PATCH 06/21] Handle buttons in the flow --- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 79 ++++++++----------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift index 9be6a390049..5a28184758e 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift @@ -25,38 +25,13 @@ enum SetupFlowState { struct SetupStep { let title: String let content: any View - let nextButtonTitle: String - let isNextButtonEnabled: Bool - let canGoBack: Bool - let onNext: () -> Void - let onBack: () -> Void init( title: String, - @ViewBuilder content: () -> any View, - nextButtonTitle: String = Localization.nextButtonTitle, - isNextButtonEnabled: Bool = true, - canGoBack: Bool = true, - onNext: @escaping () -> Void, - onBack: @escaping () -> Void + @ViewBuilder content: () -> any View ) { self.title = title self.content = content() - self.nextButtonTitle = nextButtonTitle - self.isNextButtonEnabled = isNextButtonEnabled - self.canGoBack = canGoBack - self.onNext = onNext - self.onBack = onBack - } -} - -extension SetupStep { - var shouldShowBackButton: Bool { - canGoBack - } - - var shouldShowNextButton: Bool { - true // Always show next button } } @@ -82,9 +57,23 @@ class ScannerFlow { currentStepIndex >= steps.count - 1 } + var nextButtonTitle: String { + isComplete ? Localization.doneButtonTitle : Localization.nextButtonTitle + } + + var isNextButtonEnabled: Bool { + true + } + + var shouldShowBackButton: Bool { + true + } + func nextStep() { if currentStepIndex < steps.count - 1 { currentStepIndex += 1 + } else { + onComplete() } } @@ -117,13 +106,7 @@ class ScannerFlow { return [ SetupStep( title: "General Scanner Setup", - content: { BarcodeScannerInformationContent() }, - nextButtonTitle: Localization.doneButtonTitle, - canGoBack: true, - onNext: onComplete, - onBack: { [weak self] in - self?.previousStep() - } + content: { BarcodeScannerInformationContent() } ) ] } @@ -132,13 +115,7 @@ class ScannerFlow { private func createWelcomeStep(title: String) -> SetupStep { SetupStep( title: title, - content: { ScannerWelcomeView(title: title) }, - nextButtonTitle: Localization.doneButtonTitle, - canGoBack: true, - onNext: onComplete, - onBack: { [weak self] in - self?.previousStep() - } + content: { ScannerWelcomeView(title: title) } ) } } @@ -184,6 +161,18 @@ class SetupFlowManager { func isComplete() -> Bool { currentFlow?.isComplete ?? false } + + var nextButtonTitle: String { + currentFlow?.nextButtonTitle ?? Localization.nextButtonTitle + } + + var isNextButtonEnabled: Bool { + currentFlow?.isNextButtonEnabled ?? true + } + + var shouldShowBackButton: Bool { + currentFlow?.shouldShowBackButton ?? false + } } @available(iOS 17.0, *) @@ -278,17 +267,17 @@ struct FlowButtonsView: View { var body: some View { HStack(spacing: POSSpacing.medium) { - if let step = flowManager.getCurrentStep(), step.shouldShowBackButton { + if flowManager.shouldShowBackButton { Button(Localization.backButtonTitle) { - step.onBack() + flowManager.previousStep() } .buttonStyle(POSOutlinedButtonStyle(size: .normal)) - Button(step.nextButtonTitle) { - step.onNext() + Button(flowManager.nextButtonTitle) { + flowManager.nextStep() } .buttonStyle(POSFilledButtonStyle(size: .normal)) - .disabled(!step.isNextButtonEnabled) + .disabled(!flowManager.isNextButtonEnabled) } } } From f090e1674b5938771ea1011de97ac90cb20ee0b7 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 09:21:56 +0100 Subject: [PATCH 07/21] Use a button configuration --- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 68 ++++++++++++++----- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift index 5a28184758e..ee7ca07d8ad 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift @@ -120,6 +120,27 @@ class ScannerFlow { } } +// MARK: - Button Configuration +struct ButtonConfiguration { + let shouldShowBackButton: Bool + let shouldShowNextButton: Bool + let nextButtonTitle: String + let isNextButtonEnabled: Bool + let onBack: () -> Void + let onNext: () -> Void + + static func noButtons() -> ButtonConfiguration { + .init( + shouldShowBackButton: false, + shouldShowNextButton: false, + nextButtonTitle: "", + isNextButtonEnabled: false, + onBack: {}, + onNext: {} + ) + } +} + // MARK: - Setup Flow Manager @available(iOS 17.0, *) @Observable @@ -162,16 +183,28 @@ class SetupFlowManager { currentFlow?.isComplete ?? false } - var nextButtonTitle: String { - currentFlow?.nextButtonTitle ?? Localization.nextButtonTitle - } - - var isNextButtonEnabled: Bool { - currentFlow?.isNextButtonEnabled ?? true - } + var buttonConfiguration: ButtonConfiguration { + switch currentState { + case .scannerSelection: + return .noButtons() + case .setupFlow: + guard let flow = currentFlow else { + return .noButtons() + } - var shouldShowBackButton: Bool { - currentFlow?.shouldShowBackButton ?? false + return ButtonConfiguration( + shouldShowBackButton: flow.shouldShowBackButton, + shouldShowNextButton: true, + nextButtonTitle: flow.nextButtonTitle, + isNextButtonEnabled: flow.isNextButtonEnabled, + onBack: { [weak self] in + self?.previousStep() + }, + onNext: { [weak self] in + self?.nextStep() + } + ) + } } } @@ -198,7 +231,7 @@ struct PointOfSaleBarcodeScannerSetUpFlow: View { .scrollVerticallyIfNeeded() // Bottom buttons - FlowButtonsView(flowManager: flowManager) + FlowButtonsView(buttonConfiguration: flowManager.buttonConfiguration) } .padding(POSPadding.xxLarge) .background(Color.posSurfaceBright) @@ -263,21 +296,22 @@ struct PointOfSaleBarcodeScannerSetUpFlow: View { // MARK: - Flow Buttons View @available(iOS 17.0, *) struct FlowButtonsView: View { - let flowManager: SetupFlowManager + let buttonConfiguration: ButtonConfiguration var body: some View { HStack(spacing: POSSpacing.medium) { - if flowManager.shouldShowBackButton { + if buttonConfiguration.shouldShowBackButton { Button(Localization.backButtonTitle) { - flowManager.previousStep() + buttonConfiguration.onBack() } .buttonStyle(POSOutlinedButtonStyle(size: .normal)) - - Button(flowManager.nextButtonTitle) { - flowManager.nextStep() + } + if buttonConfiguration.shouldShowNextButton { + Button(buttonConfiguration.nextButtonTitle) { + buttonConfiguration.onNext() } .buttonStyle(POSFilledButtonStyle(size: .normal)) - .disabled(!flowManager.isNextButtonEnabled) + .disabled(!buttonConfiguration.isNextButtonEnabled) } } } From 6f102c79bc520a663e9b58130603ebad04c4bb7b Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 09:45:29 +0100 Subject: [PATCH 08/21] Allow buttons to be customised --- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 105 +++++++++++++++--- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift index ee7ca07d8ad..2ef502a3758 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift @@ -21,17 +21,27 @@ enum SetupFlowState { case setupFlow(ScannerType) } +// MARK: - Step Customization Protocol +@available(iOS 17.0, *) +protocol StepCustomization { + func customizeButtons(for flow: ScannerFlow) -> ButtonConfiguration +} + // MARK: - Setup Step +@available(iOS 17.0, *) struct SetupStep { let title: String let content: any View + let customization: StepCustomization? init( title: String, - @ViewBuilder content: () -> any View + @ViewBuilder content: () -> any View, + customization: StepCustomization? = nil ) { self.title = title self.content = content() + self.customization = customization } } @@ -85,6 +95,35 @@ class ScannerFlow { } } + func restartFlow() { + currentStepIndex = 0 + } + + func getButtonConfiguration() -> ButtonConfiguration { + guard let step = currentStep else { + return .noButtons() + } + + // Use step customization if available + if let customization = step.customization { + return customization.customizeButtons(for: self) + } + + // Default button configuration + return ButtonConfiguration( + shouldShowBackButton: shouldShowBackButton, + shouldShowNextButton: true, + nextButtonTitle: nextButtonTitle, + isNextButtonEnabled: isNextButtonEnabled, + onBack: { [weak self] in + self?.previousStep() + }, + onNext: { [weak self] in + self?.nextStep() + } + ) + } + private var steps: [SetupStep] { switch scannerType { case .socketS720: @@ -115,11 +154,22 @@ class ScannerFlow { private func createWelcomeStep(title: String) -> SetupStep { SetupStep( title: title, - content: { ScannerWelcomeView(title: title) } + content: { ScannerWelcomeView(title: title) }, + customization: WelcomeStepCustomization() ) } } +// MARK: - Example Step Customizations +@available(iOS 17.0, *) +struct WelcomeStepCustomization: StepCustomization { + func customizeButtons(for flow: ScannerFlow) -> ButtonConfiguration { + return .doneOnly { + flow.nextStep() + } + } +} + // MARK: - Button Configuration struct ButtonConfiguration { let shouldShowBackButton: Bool @@ -139,6 +189,39 @@ struct ButtonConfiguration { onNext: {} ) } + + static func doneOnly(onDone: @escaping () -> Void) -> ButtonConfiguration { + .init( + shouldShowBackButton: false, + shouldShowNextButton: true, + nextButtonTitle: Localization.doneButtonTitle, + isNextButtonEnabled: true, + onBack: {}, + onNext: onDone + ) + } + + static func closeAndRetry(onClose: @escaping () -> Void, onRetry: @escaping () -> Void) -> ButtonConfiguration { + .init( + shouldShowBackButton: true, + shouldShowNextButton: true, + nextButtonTitle: Localization.retryButtonTitle, + isNextButtonEnabled: true, + onBack: onClose, + onNext: onRetry + ) + } + + static func disabledNext(onBack: @escaping () -> Void, onNext: @escaping () -> Void) -> ButtonConfiguration { + .init( + shouldShowBackButton: true, + shouldShowNextButton: true, + nextButtonTitle: Localization.nextButtonTitle, + isNextButtonEnabled: false, + onBack: onBack, + onNext: onNext + ) + } } // MARK: - Setup Flow Manager @@ -192,18 +275,7 @@ class SetupFlowManager { return .noButtons() } - return ButtonConfiguration( - shouldShowBackButton: flow.shouldShowBackButton, - shouldShowNextButton: true, - nextButtonTitle: flow.nextButtonTitle, - isNextButtonEnabled: flow.isNextButtonEnabled, - onBack: { [weak self] in - self?.previousStep() - }, - onNext: { [weak self] in - self?.nextStep() - } - ) + return flow.getButtonConfiguration() } } } @@ -464,6 +536,11 @@ private enum Localization { value: "Done", comment: "Title for the done button in barcode scanner setup navigation" ) + static let retryButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.retry.button.title", + value: "Retry", + comment: "Title for the retry button in barcode scanner setup navigation" + ) } @available(iOS 17.0, *) From 147870ad9bb9e69982f249231f0aba3dcae81fb2 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:17:38 +0100 Subject: [PATCH 09/21] Split flow classes to files --- .../DefaultFeatureFlagService.swift | 2 +- .../PointOfSaleBarcodeScannerFlow.swift | 143 +++++ .../PointOfSaleBarcodeScannerSetup.swift | 149 +++++ ...OfSaleBarcodeScannerSetupFlowManager.swift | 57 ++ ...PointOfSaleBarcodeScannerSetupModels.swift | 121 ++++ .../PointOfSaleBarcodeScannerSetupViews.swift | 123 ++++ .../Presentation/POSFloatingControlView.swift | 2 +- .../PointOfSaleBarcodeScannerSetUpFlow.swift | 549 ------------------ .../WooCommerce.xcodeproj/project.pbxproj | 32 +- 9 files changed, 623 insertions(+), 555 deletions(-) create mode 100644 WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerFlow.swift create mode 100644 WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift create mode 100644 WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift create mode 100644 WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift create mode 100644 WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift delete mode 100644 WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift diff --git a/Modules/Sources/Experiments/DefaultFeatureFlagService.swift b/Modules/Sources/Experiments/DefaultFeatureFlagService.swift index 243060fa575..4c8152c4fd6 100644 --- a/Modules/Sources/Experiments/DefaultFeatureFlagService.swift +++ b/Modules/Sources/Experiments/DefaultFeatureFlagService.swift @@ -102,7 +102,7 @@ public struct DefaultFeatureFlagService: FeatureFlagService { case .pointOfSaleOrdersi2: return buildConfig == .localDeveloper || buildConfig == .alpha case .pointOfSaleBarcodeScanningi2: - return false + return buildConfig == .localDeveloper || buildConfig == .alpha default: return true } diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerFlow.swift new file mode 100644 index 00000000000..e63a66d2058 --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerFlow.swift @@ -0,0 +1,143 @@ +import SwiftUI + +// MARK: - Point of Sale Barcode Scanner Flow +@available(iOS 17.0, *) +class PointOfSaleBarcodeScannerFlow { + private let scannerType: ScannerType + private let onComplete: () -> Void + private let onBackToSelection: () -> Void + private var currentStepIndex: Int = 0 + + init(scannerType: ScannerType, onComplete: @escaping () -> Void, onBackToSelection: @escaping () -> Void) { + self.scannerType = scannerType + self.onComplete = onComplete + self.onBackToSelection = onBackToSelection + } + + var currentStep: PointOfSaleBarcodeScannerSetupStep? { + steps[safe: currentStepIndex] + } + + var isComplete: Bool { + currentStepIndex >= steps.count - 1 + } + + var nextButtonTitle: String { + isComplete ? Localization.doneButtonTitle : Localization.nextButtonTitle + } + + var isNextButtonEnabled: Bool { + true + } + + var shouldShowBackButton: Bool { + true + } + + func nextStep() { + if currentStepIndex < steps.count - 1 { + currentStepIndex += 1 + } else { + onComplete() + } + } + + func previousStep() { + if currentStepIndex > 0 { + currentStepIndex -= 1 + } else { + onBackToSelection() + } + } + + func restartFlow() { + currentStepIndex = 0 + } + + func getButtonConfiguration() -> ButtonConfiguration { + guard let step = currentStep else { + return .noButtons() + } + + // Use step customization if available + if let customization = step.customization { + return customization.customizeButtons(for: self) + } + + // Default button configuration + return ButtonConfiguration( + shouldShowBackButton: shouldShowBackButton, + shouldShowNextButton: true, + nextButtonTitle: nextButtonTitle, + isNextButtonEnabled: isNextButtonEnabled, + onBack: { [weak self] in + self?.previousStep() + }, + onNext: { [weak self] in + self?.nextStep() + } + ) + } + + private var steps: [PointOfSaleBarcodeScannerSetupStep] { + switch scannerType { + case .socketS720: + return [ + createWelcomeStep(title: "Socket S720 Setup") + // TODO: Add more steps for Socket S720 WOOMOB-698 + ] + case .starBSH20B: + return [ + createWelcomeStep(title: "Star BSH-20B Setup") + // TODO: Add more steps for Star BSH-20B WOOMOB-696 + ] + case .tbcScanner: + return [ + createWelcomeStep(title: "TBC Scanner Setup") + // TODO: Add more steps for TBC Scanner WOOMOB-699 + ] + case .other: + return [ + PointOfSaleBarcodeScannerSetupStep( + title: "General Scanner Setup", + content: { BarcodeScannerInformationContent() } + ) + ] + } + } + + private func createWelcomeStep(title: String) -> PointOfSaleBarcodeScannerSetupStep { + PointOfSaleBarcodeScannerSetupStep( + title: title, + content: { ScannerWelcomeView(title: title) }, + customization: PointOfSaleBarcodeScannerWelcomeStepCustomization() + ) + } +} + +// MARK: - Example Step Customizations +@available(iOS 17.0, *) +struct PointOfSaleBarcodeScannerWelcomeStepCustomization: PointOfSaleBarcodeScannerStepCustomization { + func customizeButtons(for flow: PointOfSaleBarcodeScannerFlow) -> ButtonConfiguration { + return .doneOnly { + flow.nextStep() + } + } +} + +// MARK: - Private Localization Extension +@available(iOS 17.0, *) +private extension PointOfSaleBarcodeScannerFlow { + enum Localization { + static let doneButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.done.button.title", + value: "Done", + comment: "Title for the done button in barcode scanner setup navigation" + ) + static let nextButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.next.button.title", + value: "Next", + comment: "Title for the next button in barcode scanner setup navigation" + ) + } +} \ No newline at end of file diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift new file mode 100644 index 00000000000..88048449941 --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift @@ -0,0 +1,149 @@ +import SwiftUI + +@available(iOS 17.0, *) +struct PointOfSaleBarcodeScannerSetup: View { + @Binding var isPresented: Bool + @State private var flowManager: PointOfSaleBarcodeScannerSetupFlowManager + + init(isPresented: Binding) { + self._isPresented = isPresented + self.flowManager = PointOfSaleBarcodeScannerSetupFlowManager(isPresented: isPresented) + } + + var body: some View { + VStack(spacing: POSSpacing.xxLarge) { + // Header + PointOfSaleModalHeader(isPresented: $isPresented, + title: .constant(AttributedString(currentTitle))) + + VStack { + currentContent + Spacer() + } + .scrollVerticallyIfNeeded() + + // Bottom buttons + FlowButtonsView(buttonConfiguration: flowManager.buttonConfiguration) + } + .padding(POSPadding.xxLarge) + .background(Color.posSurfaceBright) + .containerRelativeFrame([.horizontal, .vertical]) { length, _ in + max(length * 0.75, Constants.modalFrameMaxSmallDimension) + } + .onAppear { + ServiceLocator.analytics.track(.pointOfSaleBarcodeScannerSetupFlowShown) + } + } + + // MARK: - Computed Properties + private var currentTitle: String { + switch flowManager.currentState { + case .scannerSelection: + return Localization.setupHeading + case .setupFlow: + return flowManager.getCurrentStep()?.title ?? Localization.setupHeading + } + } + + @ViewBuilder + private var currentContent: some View { + switch flowManager.currentState { + case .scannerSelection: + ScannerSelectionView(options: scannerOptions) { scannerType in + flowManager.selectScanner(scannerType) + } + case .setupFlow: + if let step = flowManager.getCurrentStep() { + AnyView(step.content) + } + } + } + + private var scannerOptions: [ScannerOption] { + [ + ScannerOption( + title: Localization.socketS720Title, + subtitle: Localization.socketS720Subtitle, + scannerType: .socketS720 + ), + ScannerOption( + title: Localization.starBSH20BTitle, + subtitle: Localization.starBSH20BSubtitle, + scannerType: .starBSH20B + ), + ScannerOption( + title: Localization.tbcScannerTitle, + subtitle: Localization.tbcScannerSubtitle, + scannerType: .tbcScanner + ), + ScannerOption( + title: Localization.otherTitle, + subtitle: Localization.otherSubtitle, + scannerType: .other + ) + ] + } +} + +// MARK: - Constants +private enum Constants { + static var modalFrameMaxSmallDimension: CGFloat { 752 } +} + +// MARK: - Private Localization Extension +@available(iOS 17.0, *) +private extension PointOfSaleBarcodeScannerSetup { + enum Localization { + static let setupHeading = NSLocalizedString( + "pos.barcodeScannerSetup.heading", + value: "Barcode Scanner Setup", + comment: "Heading for the barcode scanner setup flow in POS" + ) + + static let socketS720Title = NSLocalizedString( + "pos.barcodeScannerSetup.socketS720.title", + value: "Socket S720", + comment: "Title for Socket S720 scanner option in barcode scanner setup" + ) + static let socketS720Subtitle = NSLocalizedString( + "pos.barcodeScannerSetup.socketS720.subtitle", + value: "Small handheld scanner with a charging dock or stand", + comment: "Subtitle for Socket S720 scanner option in barcode scanner setup" + ) + static let starBSH20BTitle = NSLocalizedString( + "pos.barcodeScannerSetup.starBSH20B.title", + value: "Star BSH-20B", + comment: "Title for Star BSH-20B scanner option in barcode scanner setup" + ) + static let starBSH20BSubtitle = NSLocalizedString( + "pos.barcodeScannerSetup.starBSH20B.subtitle", + value: "Ergonomic scanner with a stand", + comment: "Subtitle for Star BSH-20B scanner option in barcode scanner setup" + ) + static let tbcScannerTitle = NSLocalizedString( + "pos.barcodeScannerSetup.tbcScanner.title", + value: "Scanner TBC", + comment: "Title for TBC scanner option in barcode scanner setup" + ) + static let tbcScannerSubtitle = NSLocalizedString( + "pos.barcodeScannerSetup.tbcScanner.subtitle", + value: "Recommended scanner", + comment: "Subtitle for TBC scanner option in barcode scanner setup" + ) + static let otherTitle = NSLocalizedString( + "pos.barcodeScannerSetup.other.title", + value: "Other", + comment: "Title for other scanner option in barcode scanner setup" + ) + static let otherSubtitle = NSLocalizedString( + "pos.barcodeScannerSetup.other.subtitle", + value: "General scanner setup instructions", + comment: "Subtitle for other scanner option in barcode scanner setup" + ) + } +} + +@available(iOS 17.0, *) +#Preview { + PointOfSaleBarcodeScannerSetup(isPresented: .constant(true)) +} diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift new file mode 100644 index 00000000000..51ef0b2c58f --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift @@ -0,0 +1,57 @@ +import SwiftUI + +// MARK: - Point of Sale Barcode Scanner Setup Flow Manager +@available(iOS 17.0, *) +@Observable +class PointOfSaleBarcodeScannerSetupFlowManager { + var currentState: PointOfSaleBarcodeScannerSetupFlowState = .scannerSelection + @ObservationIgnored @Binding var isPresented: Bool + private var currentFlow: PointOfSaleBarcodeScannerFlow? + + init(isPresented: Binding) { + self._isPresented = isPresented + } + + func selectScanner(_ scannerType: ScannerType) { + currentFlow = PointOfSaleBarcodeScannerFlow(scannerType: scannerType, onComplete: { [weak self] in + self?.isPresented = false + }, onBackToSelection: { [weak self] in + self?.goBackToSelection() + }) + currentState = .setupFlow(scannerType) + } + + func goBackToSelection() { + currentState = .scannerSelection + currentFlow = nil + } + + func nextStep() { + currentFlow?.nextStep() + } + + func previousStep() { + currentFlow?.previousStep() + } + + func getCurrentStep() -> PointOfSaleBarcodeScannerSetupStep? { + currentFlow?.currentStep + } + + func isComplete() -> Bool { + currentFlow?.isComplete ?? false + } + + var buttonConfiguration: ButtonConfiguration { + switch currentState { + case .scannerSelection: + return .noButtons() + case .setupFlow: + guard let flow = currentFlow else { + return .noButtons() + } + + return flow.getButtonConfiguration() + } + } +} \ No newline at end of file diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift new file mode 100644 index 00000000000..b0e55c12b72 --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -0,0 +1,121 @@ +import SwiftUI + +// MARK: - Data Models +struct ScannerOption: Identifiable { + let id = UUID() + let title: String + let subtitle: String + let scannerType: ScannerType +} + +enum ScannerType { + case socketS720 + case starBSH20B + case tbcScanner + case other +} + +// MARK: - Flow State +enum PointOfSaleBarcodeScannerSetupFlowState { + case scannerSelection + case setupFlow(ScannerType) +} + +// MARK: - Button Configuration +struct ButtonConfiguration { + let shouldShowBackButton: Bool + let shouldShowNextButton: Bool + let nextButtonTitle: String + let isNextButtonEnabled: Bool + let onBack: () -> Void + let onNext: () -> Void + + static func noButtons() -> ButtonConfiguration { + .init( + shouldShowBackButton: false, + shouldShowNextButton: false, + nextButtonTitle: "", + isNextButtonEnabled: false, + onBack: {}, + onNext: {} + ) + } + + static func doneOnly(onDone: @escaping () -> Void) -> ButtonConfiguration { + .init( + shouldShowBackButton: false, + shouldShowNextButton: true, + nextButtonTitle: Localization.doneButtonTitle, + isNextButtonEnabled: true, + onBack: {}, + onNext: onDone + ) + } + + static func closeAndRetry(onClose: @escaping () -> Void, onRetry: @escaping () -> Void) -> ButtonConfiguration { + .init( + shouldShowBackButton: true, + shouldShowNextButton: true, + nextButtonTitle: Localization.retryButtonTitle, + isNextButtonEnabled: true, + onBack: onClose, + onNext: onRetry + ) + } + + static func disabledNext(onBack: @escaping () -> Void, onNext: @escaping () -> Void) -> ButtonConfiguration { + .init( + shouldShowBackButton: true, + shouldShowNextButton: true, + nextButtonTitle: Localization.nextButtonTitle, + isNextButtonEnabled: false, + onBack: onBack, + onNext: onNext + ) + } +} + +// MARK: - Private Localization Extension +private extension ButtonConfiguration { + enum Localization { + static let doneButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.done.button.title", + value: "Done", + comment: "Title for the done button in barcode scanner setup navigation" + ) + static let retryButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.retry.button.title", + value: "Retry", + comment: "Title for the retry button in barcode scanner setup navigation" + ) + static let nextButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.next.button.title", + value: "Next", + comment: "Title for the next button in barcode scanner setup navigation" + ) + } +} + +// MARK: - Step Customization Protocol +@available(iOS 17.0, *) +protocol PointOfSaleBarcodeScannerStepCustomization { + func customizeButtons(for flow: PointOfSaleBarcodeScannerFlow) -> ButtonConfiguration +} + +// MARK: - Setup Step +@available(iOS 17.0, *) +struct PointOfSaleBarcodeScannerSetupStep { + let title: String + let content: any View + let customization: PointOfSaleBarcodeScannerStepCustomization? + + init( + title: String, + @ViewBuilder content: () -> any View, + customization: PointOfSaleBarcodeScannerStepCustomization? = nil + ) { + self.title = title + self.content = content() + self.customization = customization + } +} \ No newline at end of file diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift new file mode 100644 index 00000000000..20d92163c52 --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift @@ -0,0 +1,123 @@ +import SwiftUI + +// MARK: - Flow Buttons View +@available(iOS 17.0, *) +struct FlowButtonsView: View { + let buttonConfiguration: ButtonConfiguration + + var body: some View { + HStack(spacing: POSSpacing.medium) { + if buttonConfiguration.shouldShowBackButton { + Button(Localization.backButtonTitle) { + buttonConfiguration.onBack() + } + .buttonStyle(POSOutlinedButtonStyle(size: .normal)) + } + if buttonConfiguration.shouldShowNextButton { + Button(buttonConfiguration.nextButtonTitle) { + buttonConfiguration.onNext() + } + .buttonStyle(POSFilledButtonStyle(size: .normal)) + .disabled(!buttonConfiguration.isNextButtonEnabled) + } + } + } +} + +// MARK: - Scanner Selection View +struct ScannerSelectionView: View { + let options: [ScannerOption] + let onSelection: (ScannerType) -> Void + + var body: some View { + VStack(alignment: .leading, spacing: POSSpacing.medium) { + Text(Localization.setupIntroMessage) + .font(.posBodyLargeRegular()) + .foregroundStyle(Color.posOnSurface) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + + VStack(spacing: POSSpacing.small) { + ForEach(options) { option in + Button { + onSelection(option.scannerType) + } label: { + ScannerOptionView( + title: option.title, + subtitle: option.subtitle + ) + } + .buttonStyle(PlainButtonStyle()) + } + } + } + } +} + +// MARK: - Scanner Option View +struct ScannerOptionView: View { + let title: String + let subtitle: String + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: POSSpacing.xSmall) { + Text(title) + .font(.posBodyLargeBold) + .foregroundColor(.posOnSurface) + Text(subtitle) + .font(.posBodyMediumRegular()) + .foregroundColor(.posOnSurfaceVariantHighest) + } + Spacer() + Image(systemName: "chevron.forward") + .font(.posBodyMediumBold) + .foregroundColor(.posOnSurfaceVariantHighest) + } + .padding(POSPadding.medium) + .background(Color.posSurfaceDim) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.medium.value)) + } +} + +// MARK: - Step Views +struct ScannerWelcomeView: View { + let title: String + + var body: some View { + VStack(spacing: POSSpacing.medium) { + Text(title) + .font(.posBodyLargeBold) + .foregroundColor(.posOnSurface) + + Text("TODO: Implement \(title) setup flow") + .font(.posBodyMediumRegular()) + .foregroundColor(.posOnSurfaceVariantHighest) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +// MARK: - Private Localization Extensions + +@available(iOS 17.0, *) +private extension FlowButtonsView { + enum Localization { + static let backButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.back.button.title", + value: "Back", + comment: "Title for the back button in barcode scanner setup navigation" + ) + } +} + +private extension ScannerSelectionView { + enum Localization { + static let setupIntroMessage = NSLocalizedString( + "pos.barcodeScannerSetup.introMessage", + value: "Choose your barcode scanner to get started with the setup process.", + comment: "Introductory message in the barcode scanner setup flow in POS" + ) + } +} diff --git a/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift b/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift index 3962280106b..79fa802323a 100644 --- a/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift +++ b/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift @@ -94,7 +94,7 @@ struct POSFloatingControlView: View { } .posModal(isPresented: $showBarcodeScanningModal) { if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.pointOfSaleBarcodeScanningi2) { - PointOfSaleBarcodeScannerSetUpFlow(isPresented: $showBarcodeScanningModal) + PointOfSaleBarcodeScannerSetup(isPresented: $showBarcodeScanningModal) } else { PointOfSaleBarcodeScannerInformationModal(isPresented: $showBarcodeScanningModal) } diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift deleted file mode 100644 index 2ef502a3758..00000000000 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ /dev/null @@ -1,549 +0,0 @@ -import SwiftUI - -// MARK: - Data Models -struct ScannerOption: Identifiable { - let id = UUID() - let title: String - let subtitle: String - let scannerType: ScannerType -} - -enum ScannerType { - case socketS720 - case starBSH20B - case tbcScanner - case other -} - -// MARK: - Flow State -enum SetupFlowState { - case scannerSelection - case setupFlow(ScannerType) -} - -// MARK: - Step Customization Protocol -@available(iOS 17.0, *) -protocol StepCustomization { - func customizeButtons(for flow: ScannerFlow) -> ButtonConfiguration -} - -// MARK: - Setup Step -@available(iOS 17.0, *) -struct SetupStep { - let title: String - let content: any View - let customization: StepCustomization? - - init( - title: String, - @ViewBuilder content: () -> any View, - customization: StepCustomization? = nil - ) { - self.title = title - self.content = content() - self.customization = customization - } -} - -// MARK: - Scanner Flow -@available(iOS 17.0, *) -class ScannerFlow { - private let scannerType: ScannerType - private let onComplete: () -> Void - private let onBackToSelection: () -> Void - private var currentStepIndex: Int = 0 - - init(scannerType: ScannerType, onComplete: @escaping () -> Void, onBackToSelection: @escaping () -> Void) { - self.scannerType = scannerType - self.onComplete = onComplete - self.onBackToSelection = onBackToSelection - } - - var currentStep: SetupStep? { - steps[safe: currentStepIndex] - } - - var isComplete: Bool { - currentStepIndex >= steps.count - 1 - } - - var nextButtonTitle: String { - isComplete ? Localization.doneButtonTitle : Localization.nextButtonTitle - } - - var isNextButtonEnabled: Bool { - true - } - - var shouldShowBackButton: Bool { - true - } - - func nextStep() { - if currentStepIndex < steps.count - 1 { - currentStepIndex += 1 - } else { - onComplete() - } - } - - func previousStep() { - if currentStepIndex > 0 { - currentStepIndex -= 1 - } else { - onBackToSelection() - } - } - - func restartFlow() { - currentStepIndex = 0 - } - - func getButtonConfiguration() -> ButtonConfiguration { - guard let step = currentStep else { - return .noButtons() - } - - // Use step customization if available - if let customization = step.customization { - return customization.customizeButtons(for: self) - } - - // Default button configuration - return ButtonConfiguration( - shouldShowBackButton: shouldShowBackButton, - shouldShowNextButton: true, - nextButtonTitle: nextButtonTitle, - isNextButtonEnabled: isNextButtonEnabled, - onBack: { [weak self] in - self?.previousStep() - }, - onNext: { [weak self] in - self?.nextStep() - } - ) - } - - private var steps: [SetupStep] { - switch scannerType { - case .socketS720: - return [ - createWelcomeStep(title: "Socket S720 Setup") - // TODO: Add more steps for Socket S720 WOOMOB-698 - ] - case .starBSH20B: - return [ - createWelcomeStep(title: "Star BSH-20B Setup") - // TODO: Add more steps for Star BSH-20B WOOMOB-696 - ] - case .tbcScanner: - return [ - createWelcomeStep(title: "TBC Scanner Setup") - // TODO: Add more steps for TBC Scanner WOOMOB-699 - ] - case .other: - return [ - SetupStep( - title: "General Scanner Setup", - content: { BarcodeScannerInformationContent() } - ) - ] - } - } - - private func createWelcomeStep(title: String) -> SetupStep { - SetupStep( - title: title, - content: { ScannerWelcomeView(title: title) }, - customization: WelcomeStepCustomization() - ) - } -} - -// MARK: - Example Step Customizations -@available(iOS 17.0, *) -struct WelcomeStepCustomization: StepCustomization { - func customizeButtons(for flow: ScannerFlow) -> ButtonConfiguration { - return .doneOnly { - flow.nextStep() - } - } -} - -// MARK: - Button Configuration -struct ButtonConfiguration { - let shouldShowBackButton: Bool - let shouldShowNextButton: Bool - let nextButtonTitle: String - let isNextButtonEnabled: Bool - let onBack: () -> Void - let onNext: () -> Void - - static func noButtons() -> ButtonConfiguration { - .init( - shouldShowBackButton: false, - shouldShowNextButton: false, - nextButtonTitle: "", - isNextButtonEnabled: false, - onBack: {}, - onNext: {} - ) - } - - static func doneOnly(onDone: @escaping () -> Void) -> ButtonConfiguration { - .init( - shouldShowBackButton: false, - shouldShowNextButton: true, - nextButtonTitle: Localization.doneButtonTitle, - isNextButtonEnabled: true, - onBack: {}, - onNext: onDone - ) - } - - static func closeAndRetry(onClose: @escaping () -> Void, onRetry: @escaping () -> Void) -> ButtonConfiguration { - .init( - shouldShowBackButton: true, - shouldShowNextButton: true, - nextButtonTitle: Localization.retryButtonTitle, - isNextButtonEnabled: true, - onBack: onClose, - onNext: onRetry - ) - } - - static func disabledNext(onBack: @escaping () -> Void, onNext: @escaping () -> Void) -> ButtonConfiguration { - .init( - shouldShowBackButton: true, - shouldShowNextButton: true, - nextButtonTitle: Localization.nextButtonTitle, - isNextButtonEnabled: false, - onBack: onBack, - onNext: onNext - ) - } -} - -// MARK: - Setup Flow Manager -@available(iOS 17.0, *) -@Observable -class SetupFlowManager { - var currentState: SetupFlowState = .scannerSelection - @ObservationIgnored @Binding var isPresented: Bool - private var currentFlow: ScannerFlow? - - init(isPresented: Binding) { - self._isPresented = isPresented - } - - func selectScanner(_ scannerType: ScannerType) { - currentFlow = ScannerFlow(scannerType: scannerType, onComplete: { [weak self] in - self?.isPresented = false - }, onBackToSelection: { [weak self] in - self?.goBackToSelection() - }) - currentState = .setupFlow(scannerType) - } - - func goBackToSelection() { - currentState = .scannerSelection - currentFlow = nil - } - - func nextStep() { - currentFlow?.nextStep() - } - - func previousStep() { - currentFlow?.previousStep() - } - - func getCurrentStep() -> SetupStep? { - currentFlow?.currentStep - } - - func isComplete() -> Bool { - currentFlow?.isComplete ?? false - } - - var buttonConfiguration: ButtonConfiguration { - switch currentState { - case .scannerSelection: - return .noButtons() - case .setupFlow: - guard let flow = currentFlow else { - return .noButtons() - } - - return flow.getButtonConfiguration() - } - } -} - -@available(iOS 17.0, *) -struct PointOfSaleBarcodeScannerSetUpFlow: View { - @Binding var isPresented: Bool - @State private var flowManager: SetupFlowManager - - init(isPresented: Binding) { - self._isPresented = isPresented - self.flowManager = SetupFlowManager(isPresented: isPresented) - } - - var body: some View { - VStack(spacing: POSSpacing.xxLarge) { - // Header - PointOfSaleModalHeader(isPresented: $isPresented, - title: .constant(AttributedString(currentTitle))) - - VStack { - currentContent - Spacer() - } - .scrollVerticallyIfNeeded() - - // Bottom buttons - FlowButtonsView(buttonConfiguration: flowManager.buttonConfiguration) - } - .padding(POSPadding.xxLarge) - .background(Color.posSurfaceBright) - .containerRelativeFrame([.horizontal, .vertical]) { length, _ in - max(length * 0.75, Constants.modalFrameMaxSmallDimension) - } - .onAppear { - ServiceLocator.analytics.track(.pointOfSaleBarcodeScannerSetupFlowShown) - } - } - - // MARK: - Computed Properties - private var currentTitle: String { - switch flowManager.currentState { - case .scannerSelection: - return Localization.setupHeading - case .setupFlow: - return flowManager.getCurrentStep()?.title ?? Localization.setupHeading - } - } - - @ViewBuilder - private var currentContent: some View { - switch flowManager.currentState { - case .scannerSelection: - ScannerSelectionView(options: scannerOptions) { scannerType in - flowManager.selectScanner(scannerType) - } - case .setupFlow: - if let step = flowManager.getCurrentStep() { - AnyView(step.content) - } - } - } - - private var scannerOptions: [ScannerOption] { - [ - ScannerOption( - title: Localization.socketS720Title, - subtitle: Localization.socketS720Subtitle, - scannerType: .socketS720 - ), - ScannerOption( - title: Localization.starBSH20BTitle, - subtitle: Localization.starBSH20BSubtitle, - scannerType: .starBSH20B - ), - ScannerOption( - title: Localization.tbcScannerTitle, - subtitle: Localization.tbcScannerSubtitle, - scannerType: .tbcScanner - ), - ScannerOption( - title: Localization.otherTitle, - subtitle: Localization.otherSubtitle, - scannerType: .other - ) - ] - } -} - -// MARK: - Flow Buttons View -@available(iOS 17.0, *) -struct FlowButtonsView: View { - let buttonConfiguration: ButtonConfiguration - - var body: some View { - HStack(spacing: POSSpacing.medium) { - if buttonConfiguration.shouldShowBackButton { - Button(Localization.backButtonTitle) { - buttonConfiguration.onBack() - } - .buttonStyle(POSOutlinedButtonStyle(size: .normal)) - } - if buttonConfiguration.shouldShowNextButton { - Button(buttonConfiguration.nextButtonTitle) { - buttonConfiguration.onNext() - } - .buttonStyle(POSFilledButtonStyle(size: .normal)) - .disabled(!buttonConfiguration.isNextButtonEnabled) - } - } - } -} - -// MARK: - Scanner Selection View -struct ScannerSelectionView: View { - let options: [ScannerOption] - let onSelection: (ScannerType) -> Void - - var body: some View { - VStack(alignment: .leading, spacing: POSSpacing.medium) { - Text(Localization.setupIntroMessage) - .font(.posBodyLargeRegular()) - .foregroundStyle(Color.posOnSurface) - .multilineTextAlignment(.leading) - .fixedSize(horizontal: false, vertical: true) - - VStack(spacing: POSSpacing.small) { - ForEach(options) { option in - Button { - onSelection(option.scannerType) - } label: { - ScannerOptionView( - title: option.title, - subtitle: option.subtitle - ) - } - .buttonStyle(PlainButtonStyle()) - } - } - } - } -} - -// MARK: - Scanner Option View -struct ScannerOptionView: View { - let title: String - let subtitle: String - - var body: some View { - HStack { - VStack(alignment: .leading, spacing: POSSpacing.xSmall) { - Text(title) - .font(.posBodyLargeBold) - .foregroundColor(.posOnSurface) - Text(subtitle) - .font(.posBodyMediumRegular()) - .foregroundColor(.posOnSurfaceVariantHighest) - } - Spacer() - Image(systemName: "chevron.forward") - .font(.posBodyMediumBold) - .foregroundColor(.posOnSurfaceVariantHighest) - } - .padding(POSPadding.medium) - .background(Color.posSurfaceDim) - .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.medium.value)) - } -} - -// MARK: - Step Views -struct ScannerWelcomeView: View { - let title: String - - var body: some View { - VStack(spacing: POSSpacing.medium) { - Text(title) - .font(.posBodyLargeBold) - .foregroundColor(.posOnSurface) - - Text("TODO: Implement \(title) setup flow") - .font(.posBodyMediumRegular()) - .foregroundColor(.posOnSurfaceVariantHighest) - .multilineTextAlignment(.center) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} - -// MARK: - Constants -private enum Constants { - static var modalFrameMaxSmallDimension: CGFloat { 752 } -} - -// MARK: - Localization -private enum Localization { - static let setupHeading = NSLocalizedString( - "pos.barcodeScannerSetup.heading", - value: "Barcode Scanner Setup", - comment: "Heading for the barcode scanner setup flow in POS" - ) - static let setupIntroMessage = NSLocalizedString( - "pos.barcodeScannerSetup.introMessage", - value: "Choose your barcode scanner to get started with the setup process.", - comment: "Introductory message in the barcode scanner setup flow in POS" - ) - static let socketS720Title = NSLocalizedString( - "pos.barcodeScannerSetup.socketS720.title", - value: "Socket S720", - comment: "Title for Socket S720 scanner option in barcode scanner setup" - ) - static let socketS720Subtitle = NSLocalizedString( - "pos.barcodeScannerSetup.socketS720.subtitle", - value: "Small handheld scanner with a charging dock or stand", - comment: "Subtitle for Socket S720 scanner option in barcode scanner setup" - ) - static let starBSH20BTitle = NSLocalizedString( - "pos.barcodeScannerSetup.starBSH20B.title", - value: "Star BSH-20B", - comment: "Title for Star BSH-20B scanner option in barcode scanner setup" - ) - static let starBSH20BSubtitle = NSLocalizedString( - "pos.barcodeScannerSetup.starBSH20B.subtitle", - value: "Ergonomic scanner with a stand", - comment: "Subtitle for Star BSH-20B scanner option in barcode scanner setup" - ) - static let tbcScannerTitle = NSLocalizedString( - "pos.barcodeScannerSetup.tbcScanner.title", - value: "Scanner TBC", - comment: "Title for TBC scanner option in barcode scanner setup" - ) - static let tbcScannerSubtitle = NSLocalizedString( - "pos.barcodeScannerSetup.tbcScanner.subtitle", - value: "Recommended scanner", - comment: "Subtitle for TBC scanner option in barcode scanner setup" - ) - static let otherTitle = NSLocalizedString( - "pos.barcodeScannerSetup.other.title", - value: "Other", - comment: "Title for other scanner option in barcode scanner setup" - ) - static let otherSubtitle = NSLocalizedString( - "pos.barcodeScannerSetup.other.subtitle", - value: "General scanner setup instructions", - comment: "Subtitle for other scanner option in barcode scanner setup" - ) - static let backButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.back.button.title", - value: "Back", - comment: "Title for the back button in barcode scanner setup navigation" - ) - static let nextButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.next.button.title", - value: "Next", - comment: "Title for the next button in barcode scanner setup navigation" - ) - static let doneButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.done.button.title", - value: "Done", - comment: "Title for the done button in barcode scanner setup navigation" - ) - static let retryButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.retry.button.title", - value: "Retry", - comment: "Title for the retry button in barcode scanner setup navigation" - ) -} - -@available(iOS 17.0, *) -#Preview { - PointOfSaleBarcodeScannerSetUpFlow(isPresented: .constant(true)) -} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 67c18a5a332..65166865901 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -912,8 +912,12 @@ 20BCF6EE2B0E478B00954840 /* WooPaymentsPayoutsOverviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20BCF6ED2B0E478B00954840 /* WooPaymentsPayoutsOverviewViewModel.swift */; }; 20BCF6F02B0E48CC00954840 /* WooPaymentsPayoutsOverviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20BCF6EF2B0E48CC00954840 /* WooPaymentsPayoutsOverviewViewModelTests.swift */; }; 20BCF6F72B0E5AF000954840 /* MockSystemStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20BCF6F62B0E5AEF00954840 /* MockSystemStatusService.swift */; }; - 20C3C8822E1D11F500CF7D3B /* PointOfSaleBarcodeScannerSetUpFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3C8812E1D11F500CF7D3B /* PointOfSaleBarcodeScannerSetUpFlow.swift */; }; 20C3CC3C2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3CC3B2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift */; }; + 20C3DB232E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */; }; + 20C3DB242E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */; }; + 20C3DB252E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */; }; + 20C3DB262E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */; }; + 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift */; }; 20C6E7512CDE4AEA00CD124C /* ItemListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */; }; 20C909962D3151FA0013BCCF /* ItemListBaseItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C909952D3151FA0013BCCF /* ItemListBaseItem.swift */; }; 20CC1EDB2AFA8381006BD429 /* InPersonPaymentsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20CC1EDA2AFA8381006BD429 /* InPersonPaymentsMenu.swift */; }; @@ -4077,8 +4081,12 @@ 20BCF6ED2B0E478B00954840 /* WooPaymentsPayoutsOverviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooPaymentsPayoutsOverviewViewModel.swift; sourceTree = ""; }; 20BCF6EF2B0E48CC00954840 /* WooPaymentsPayoutsOverviewViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooPaymentsPayoutsOverviewViewModelTests.swift; sourceTree = ""; }; 20BCF6F62B0E5AEF00954840 /* MockSystemStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSystemStatusService.swift; sourceTree = ""; }; - 20C3C8812E1D11F500CF7D3B /* PointOfSaleBarcodeScannerSetUpFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetUpFlow.swift; sourceTree = ""; }; 20C3CC3B2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleModalHeader.swift; sourceTree = ""; }; + 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerFlow.swift; sourceTree = ""; }; + 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetup.swift; sourceTree = ""; }; + 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupFlowManager.swift; sourceTree = ""; }; + 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupModels.swift; sourceTree = ""; }; + 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupViews.swift; sourceTree = ""; }; 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListState.swift; sourceTree = ""; }; 20C909952D3151FA0013BCCF /* ItemListBaseItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListBaseItem.swift; sourceTree = ""; }; 20CC1EDA2AFA8381006BD429 /* InPersonPaymentsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsMenu.swift; sourceTree = ""; }; @@ -7092,6 +7100,7 @@ 026826A12BF59DED0036F959 /* Presentation */ = { isa = PBXGroup; children = ( + 20C3DB222E1E69CF00CF7D3B /* Barcode Scanner Setup */, 20D557572DF9D57800D9EC8B /* Barcode Scanning */, 016A77672D9D24A30004FCD6 /* Coupons */, 026A50262D2F6BBF002C42C2 /* Infinite Scroll */, @@ -7112,7 +7121,6 @@ 01435CF72DFC2CE800C0279B /* PointOfSaleInformationModal.swift */, 20C3CC3B2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift */, 014371262DFC8E2100C0279B /* PointOfSaleBarcodeScannerInformationModal.swift */, - 20C3C8812E1D11F500CF7D3B /* PointOfSaleBarcodeScannerSetUpFlow.swift */, 68C53CBD2C1FE59B00C6D80B /* ItemListView.swift */, 026826A32BF59DF60036F959 /* CartView.swift */, 026826A22BF59DF60036F959 /* ItemRowView.swift */, @@ -8292,6 +8300,18 @@ path = AddOrderComponentsSection; sourceTree = ""; }; + 20C3DB222E1E69CF00CF7D3B /* Barcode Scanner Setup */ = { + isa = PBXGroup; + children = ( + 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift */, + 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */, + 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */, + 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */, + 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */, + ); + path = "Barcode Scanner Setup"; + sourceTree = ""; + }; 20CCBF1F2B0E159D003102E6 /* Deposits Overview */ = { isa = PBXGroup; children = ( @@ -15919,6 +15939,11 @@ EECB6D1E2AFBFE0000040BC9 /* WooSubscriptionProductsEligibilityChecker.swift in Sources */, CEA455C72BB5CA5E00D932CF /* AnalyticsSessionsUnavailableCard.swift in Sources */, EE45E29D2A381A250085F227 /* ProductDescriptionGenerationCelebrationView.swift in Sources */, + 20C3DB232E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift in Sources */, + 20C3DB242E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift in Sources */, + 20C3DB252E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift in Sources */, + 20C3DB262E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift in Sources */, + 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift in Sources */, 0260B1B12805321B00FCFE8C /* OrderDetailsPaymentAlertsProtocol.swift in Sources */, 4556ED38270645A6005CBC0D /* ShippingLabelCarrierSectionHeader.swift in Sources */, 267C01CF29E89E1700FCC97B /* StorePlanSynchronizer.swift in Sources */, @@ -16079,7 +16104,6 @@ CEE006082077D14C0079161F /* OrderDetailsViewController.swift in Sources */, 01B744E22D2FCA1400AEB3F4 /* PushNotificationBackgroundSynchronizerFactory.swift in Sources */, CE4ECA582BC5B66A005F9386 /* WCAnalyticsStatsTotals+UI.swift in Sources */, - 20C3C8822E1D11F500CF7D3B /* PointOfSaleBarcodeScannerSetUpFlow.swift in Sources */, 01FB19582C6E901800A44FF0 /* DynamicHStack.swift in Sources */, AEB73C0C25CD734200A8454A /* AttributePickerViewModel.swift in Sources */, 2005D3F32DC13D6900E12021 /* PointOfSaleItemListAnalyticsTracker.swift in Sources */, From edaf727760cda5571b60b2ce5d905bc54050c320 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:23:22 +0100 Subject: [PATCH 10/21] Rename Flow to SetupFlow --- ....swift => PointOfSaleBarcodeScannerSetupFlow.swift} | 10 +++++----- .../PointOfSaleBarcodeScannerSetupFlowManager.swift | 6 +++--- .../PointOfSaleBarcodeScannerSetupModels.swift | 4 ++-- WooCommerce/WooCommerce.xcodeproj/project.pbxproj | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) rename WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/{PointOfSaleBarcodeScannerFlow.swift => PointOfSaleBarcodeScannerSetupFlow.swift} (94%) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift similarity index 94% rename from WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerFlow.swift rename to WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift index e63a66d2058..90133dcc09d 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -1,8 +1,8 @@ import SwiftUI -// MARK: - Point of Sale Barcode Scanner Flow +// MARK: - Point of Sale Barcode Scanner Setup Flow @available(iOS 17.0, *) -class PointOfSaleBarcodeScannerFlow { +class PointOfSaleBarcodeScannerSetupFlow { private let scannerType: ScannerType private let onComplete: () -> Void private let onBackToSelection: () -> Void @@ -118,7 +118,7 @@ class PointOfSaleBarcodeScannerFlow { // MARK: - Example Step Customizations @available(iOS 17.0, *) struct PointOfSaleBarcodeScannerWelcomeStepCustomization: PointOfSaleBarcodeScannerStepCustomization { - func customizeButtons(for flow: PointOfSaleBarcodeScannerFlow) -> ButtonConfiguration { + func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> ButtonConfiguration { return .doneOnly { flow.nextStep() } @@ -127,7 +127,7 @@ struct PointOfSaleBarcodeScannerWelcomeStepCustomization: PointOfSaleBarcodeScan // MARK: - Private Localization Extension @available(iOS 17.0, *) -private extension PointOfSaleBarcodeScannerFlow { +private extension PointOfSaleBarcodeScannerSetupFlow { enum Localization { static let doneButtonTitle = NSLocalizedString( "pos.barcodeScannerSetup.done.button.title", @@ -140,4 +140,4 @@ private extension PointOfSaleBarcodeScannerFlow { comment: "Title for the next button in barcode scanner setup navigation" ) } -} \ No newline at end of file +} diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift index 51ef0b2c58f..413daab884e 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift @@ -6,14 +6,14 @@ import SwiftUI class PointOfSaleBarcodeScannerSetupFlowManager { var currentState: PointOfSaleBarcodeScannerSetupFlowState = .scannerSelection @ObservationIgnored @Binding var isPresented: Bool - private var currentFlow: PointOfSaleBarcodeScannerFlow? + private var currentFlow: PointOfSaleBarcodeScannerSetupFlow? init(isPresented: Binding) { self._isPresented = isPresented } func selectScanner(_ scannerType: ScannerType) { - currentFlow = PointOfSaleBarcodeScannerFlow(scannerType: scannerType, onComplete: { [weak self] in + currentFlow = PointOfSaleBarcodeScannerSetupFlow(scannerType: scannerType, onComplete: { [weak self] in self?.isPresented = false }, onBackToSelection: { [weak self] in self?.goBackToSelection() @@ -54,4 +54,4 @@ class PointOfSaleBarcodeScannerSetupFlowManager { return flow.getButtonConfiguration() } } -} \ No newline at end of file +} diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift index b0e55c12b72..7902f825a0d 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -99,7 +99,7 @@ private extension ButtonConfiguration { // MARK: - Step Customization Protocol @available(iOS 17.0, *) protocol PointOfSaleBarcodeScannerStepCustomization { - func customizeButtons(for flow: PointOfSaleBarcodeScannerFlow) -> ButtonConfiguration + func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> ButtonConfiguration } // MARK: - Setup Step @@ -118,4 +118,4 @@ struct PointOfSaleBarcodeScannerSetupStep { self.content = content() self.customization = customization } -} \ No newline at end of file +} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 65166865901..a27d0c030c3 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -917,7 +917,7 @@ 20C3DB242E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */; }; 20C3DB252E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */; }; 20C3DB262E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */; }; - 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift */; }; + 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift */; }; 20C6E7512CDE4AEA00CD124C /* ItemListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */; }; 20C909962D3151FA0013BCCF /* ItemListBaseItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C909952D3151FA0013BCCF /* ItemListBaseItem.swift */; }; 20CC1EDB2AFA8381006BD429 /* InPersonPaymentsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20CC1EDA2AFA8381006BD429 /* InPersonPaymentsMenu.swift */; }; @@ -4082,7 +4082,7 @@ 20BCF6EF2B0E48CC00954840 /* WooPaymentsPayoutsOverviewViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooPaymentsPayoutsOverviewViewModelTests.swift; sourceTree = ""; }; 20BCF6F62B0E5AEF00954840 /* MockSystemStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSystemStatusService.swift; sourceTree = ""; }; 20C3CC3B2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleModalHeader.swift; sourceTree = ""; }; - 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerFlow.swift; sourceTree = ""; }; + 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupFlow.swift; sourceTree = ""; }; 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetup.swift; sourceTree = ""; }; 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupFlowManager.swift; sourceTree = ""; }; 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupModels.swift; sourceTree = ""; }; @@ -8303,7 +8303,7 @@ 20C3DB222E1E69CF00CF7D3B /* Barcode Scanner Setup */ = { isa = PBXGroup; children = ( - 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift */, + 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift */, 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */, 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */, 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */, @@ -15943,7 +15943,7 @@ 20C3DB242E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift in Sources */, 20C3DB252E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift in Sources */, 20C3DB262E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift in Sources */, - 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerFlow.swift in Sources */, + 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift in Sources */, 0260B1B12805321B00FCFE8C /* OrderDetailsPaymentAlertsProtocol.swift in Sources */, 4556ED38270645A6005CBC0D /* ShippingLabelCarrierSectionHeader.swift in Sources */, 267C01CF29E89E1700FCC97B /* StorePlanSynchronizer.swift in Sources */, From 049b3b2ddef9f0c88a1e8f3309203e9e14862764 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:24:42 +0100 Subject: [PATCH 11/21] Rename ScannerOption --- .../PointOfSaleBarcodeScannerSetup.swift | 10 +++++----- .../PointOfSaleBarcodeScannerSetupModels.swift | 2 +- .../PointOfSaleBarcodeScannerSetupViews.swift | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift index 88048449941..0779f8e8b39 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift @@ -59,24 +59,24 @@ struct PointOfSaleBarcodeScannerSetup: View { } } - private var scannerOptions: [ScannerOption] { + private var scannerOptions: [PointOfSaleBarcodeScannerSetupFlowOption] { [ - ScannerOption( + PointOfSaleBarcodeScannerSetupFlowOption( title: Localization.socketS720Title, subtitle: Localization.socketS720Subtitle, scannerType: .socketS720 ), - ScannerOption( + PointOfSaleBarcodeScannerSetupFlowOption( title: Localization.starBSH20BTitle, subtitle: Localization.starBSH20BSubtitle, scannerType: .starBSH20B ), - ScannerOption( + PointOfSaleBarcodeScannerSetupFlowOption( title: Localization.tbcScannerTitle, subtitle: Localization.tbcScannerSubtitle, scannerType: .tbcScanner ), - ScannerOption( + PointOfSaleBarcodeScannerSetupFlowOption( title: Localization.otherTitle, subtitle: Localization.otherSubtitle, scannerType: .other diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift index 7902f825a0d..d1670b36fd0 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -1,7 +1,7 @@ import SwiftUI // MARK: - Data Models -struct ScannerOption: Identifiable { +struct PointOfSaleBarcodeScannerSetupFlowOption: Identifiable { let id = UUID() let title: String let subtitle: String diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift index 20d92163c52..82e1a80ea36 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift @@ -26,7 +26,7 @@ struct FlowButtonsView: View { // MARK: - Scanner Selection View struct ScannerSelectionView: View { - let options: [ScannerOption] + let options: [PointOfSaleBarcodeScannerSetupFlowOption] let onSelection: (ScannerType) -> Void var body: some View { From 34d44f734de9e769966ca00a44d52ee76b930c22 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:29:26 +0100 Subject: [PATCH 12/21] Rename ScannerType for specificity --- .../PointOfSaleBarcodeScannerSetupFlow.swift | 6 ++++-- .../PointOfSaleBarcodeScannerSetupFlowManager.swift | 2 +- .../PointOfSaleBarcodeScannerSetupModels.swift | 6 +++--- .../PointOfSaleBarcodeScannerSetupViews.swift | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift index 90133dcc09d..1685a77c212 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -3,12 +3,14 @@ import SwiftUI // MARK: - Point of Sale Barcode Scanner Setup Flow @available(iOS 17.0, *) class PointOfSaleBarcodeScannerSetupFlow { - private let scannerType: ScannerType + private let scannerType: PointOfSaleBarcodeScannerType private let onComplete: () -> Void private let onBackToSelection: () -> Void private var currentStepIndex: Int = 0 - init(scannerType: ScannerType, onComplete: @escaping () -> Void, onBackToSelection: @escaping () -> Void) { + init(scannerType: PointOfSaleBarcodeScannerType, + onComplete: @escaping () -> Void, + onBackToSelection: @escaping () -> Void) { self.scannerType = scannerType self.onComplete = onComplete self.onBackToSelection = onBackToSelection diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift index 413daab884e..3fd4ee4f704 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift @@ -12,7 +12,7 @@ class PointOfSaleBarcodeScannerSetupFlowManager { self._isPresented = isPresented } - func selectScanner(_ scannerType: ScannerType) { + func selectScanner(_ scannerType: PointOfSaleBarcodeScannerType) { currentFlow = PointOfSaleBarcodeScannerSetupFlow(scannerType: scannerType, onComplete: { [weak self] in self?.isPresented = false }, onBackToSelection: { [weak self] in diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift index d1670b36fd0..d3edfe1d8ad 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -5,10 +5,10 @@ struct PointOfSaleBarcodeScannerSetupFlowOption: Identifiable { let id = UUID() let title: String let subtitle: String - let scannerType: ScannerType + let scannerType: PointOfSaleBarcodeScannerType } -enum ScannerType { +enum PointOfSaleBarcodeScannerType { case socketS720 case starBSH20B case tbcScanner @@ -18,7 +18,7 @@ enum ScannerType { // MARK: - Flow State enum PointOfSaleBarcodeScannerSetupFlowState { case scannerSelection - case setupFlow(ScannerType) + case setupFlow(PointOfSaleBarcodeScannerType) } // MARK: - Button Configuration diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift index 82e1a80ea36..9a9ae907e50 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift @@ -27,7 +27,7 @@ struct FlowButtonsView: View { // MARK: - Scanner Selection View struct ScannerSelectionView: View { let options: [PointOfSaleBarcodeScannerSetupFlowOption] - let onSelection: (ScannerType) -> Void + let onSelection: (PointOfSaleBarcodeScannerType) -> Void var body: some View { VStack(alignment: .leading, spacing: POSSpacing.medium) { From 99a862b3fd7395a6e098446c3bbef418417e0f59 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:34:20 +0100 Subject: [PATCH 13/21] Rename buttonConfiguration for specificity --- .../PointOfSaleBarcodeScannerSetupFlow.swift | 6 ++--- ...OfSaleBarcodeScannerSetupFlowManager.swift | 2 +- ...PointOfSaleBarcodeScannerSetupModels.swift | 24 ++++++++++--------- .../PointOfSaleBarcodeScannerSetupViews.swift | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift index 1685a77c212..ea455fd9cff 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -56,7 +56,7 @@ class PointOfSaleBarcodeScannerSetupFlow { currentStepIndex = 0 } - func getButtonConfiguration() -> ButtonConfiguration { + func getButtonConfiguration() -> PointOfSaleFlowButtonConfiguration { guard let step = currentStep else { return .noButtons() } @@ -67,7 +67,7 @@ class PointOfSaleBarcodeScannerSetupFlow { } // Default button configuration - return ButtonConfiguration( + return PointOfSaleFlowButtonConfiguration( shouldShowBackButton: shouldShowBackButton, shouldShowNextButton: true, nextButtonTitle: nextButtonTitle, @@ -120,7 +120,7 @@ class PointOfSaleBarcodeScannerSetupFlow { // MARK: - Example Step Customizations @available(iOS 17.0, *) struct PointOfSaleBarcodeScannerWelcomeStepCustomization: PointOfSaleBarcodeScannerStepCustomization { - func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> ButtonConfiguration { + func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration { return .doneOnly { flow.nextStep() } diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift index 3fd4ee4f704..f4bff61f6ab 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift @@ -42,7 +42,7 @@ class PointOfSaleBarcodeScannerSetupFlowManager { currentFlow?.isComplete ?? false } - var buttonConfiguration: ButtonConfiguration { + var buttonConfiguration: PointOfSaleFlowButtonConfiguration { switch currentState { case .scannerSelection: return .noButtons() diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift index d3edfe1d8ad..44dbce181d8 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -22,7 +22,7 @@ enum PointOfSaleBarcodeScannerSetupFlowState { } // MARK: - Button Configuration -struct ButtonConfiguration { +struct PointOfSaleFlowButtonConfiguration { let shouldShowBackButton: Bool let shouldShowNextButton: Bool let nextButtonTitle: String @@ -30,7 +30,7 @@ struct ButtonConfiguration { let onBack: () -> Void let onNext: () -> Void - static func noButtons() -> ButtonConfiguration { + static func noButtons() -> PointOfSaleFlowButtonConfiguration { .init( shouldShowBackButton: false, shouldShowNextButton: false, @@ -41,7 +41,7 @@ struct ButtonConfiguration { ) } - static func doneOnly(onDone: @escaping () -> Void) -> ButtonConfiguration { + static func doneOnly(onDone: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { .init( shouldShowBackButton: false, shouldShowNextButton: true, @@ -52,7 +52,8 @@ struct ButtonConfiguration { ) } - static func closeAndRetry(onClose: @escaping () -> Void, onRetry: @escaping () -> Void) -> ButtonConfiguration { + static func closeAndRetry(onClose: @escaping () -> Void, + onRetry: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { .init( shouldShowBackButton: true, shouldShowNextButton: true, @@ -63,7 +64,8 @@ struct ButtonConfiguration { ) } - static func disabledNext(onBack: @escaping () -> Void, onNext: @escaping () -> Void) -> ButtonConfiguration { + static func disabledNext(onBack: @escaping () -> Void, + onNext: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { .init( shouldShowBackButton: true, shouldShowNextButton: true, @@ -76,22 +78,22 @@ struct ButtonConfiguration { } // MARK: - Private Localization Extension -private extension ButtonConfiguration { +private extension PointOfSaleFlowButtonConfiguration { enum Localization { static let doneButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.done.button.title", + "pos.flow.done.button.title", value: "Done", - comment: "Title for the done button in barcode scanner setup navigation" + comment: "Title for the done button in a step by step flow view in POS" ) static let retryButtonTitle = NSLocalizedString( "pos.barcodeScannerSetup.retry.button.title", value: "Retry", - comment: "Title for the retry button in barcode scanner setup navigation" + comment: "Title for the retry button in a step by step flow view in POS" ) static let nextButtonTitle = NSLocalizedString( "pos.barcodeScannerSetup.next.button.title", value: "Next", - comment: "Title for the next button in barcode scanner setup navigation" + comment: "Title for the next button in a step by step flow view in POS" ) } } @@ -99,7 +101,7 @@ private extension ButtonConfiguration { // MARK: - Step Customization Protocol @available(iOS 17.0, *) protocol PointOfSaleBarcodeScannerStepCustomization { - func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> ButtonConfiguration + func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration } // MARK: - Setup Step diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift index 9a9ae907e50..fa0067ca384 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift @@ -3,7 +3,7 @@ import SwiftUI // MARK: - Flow Buttons View @available(iOS 17.0, *) struct FlowButtonsView: View { - let buttonConfiguration: ButtonConfiguration + let buttonConfiguration: PointOfSaleFlowButtonConfiguration var body: some View { HStack(spacing: POSSpacing.medium) { From aee298606df003cab91303aa521fe31cffd996d8 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:41:58 +0100 Subject: [PATCH 14/21] Move reusable flow buttons out --- .../PointOfSaleBarcodeScannerSetup.swift | 2 +- ...PointOfSaleBarcodeScannerSetupModels.swift | 77 ------------ .../PointOfSaleBarcodeScannerSetupViews.swift | 36 ------ .../PointOfSaleFlowButtonsView.swift | 112 ++++++++++++++++++ .../WooCommerce.xcodeproj/project.pbxproj | 4 + 5 files changed, 117 insertions(+), 114 deletions(-) create mode 100644 WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift index 0779f8e8b39..04e002359f3 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift @@ -23,7 +23,7 @@ struct PointOfSaleBarcodeScannerSetup: View { .scrollVerticallyIfNeeded() // Bottom buttons - FlowButtonsView(buttonConfiguration: flowManager.buttonConfiguration) + PointOfSaleFlowButtonsView(configuration: flowManager.buttonConfiguration) } .padding(POSPadding.xxLarge) .background(Color.posSurfaceBright) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift index 44dbce181d8..a9e1e5dc0f5 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -21,83 +21,6 @@ enum PointOfSaleBarcodeScannerSetupFlowState { case setupFlow(PointOfSaleBarcodeScannerType) } -// MARK: - Button Configuration -struct PointOfSaleFlowButtonConfiguration { - let shouldShowBackButton: Bool - let shouldShowNextButton: Bool - let nextButtonTitle: String - let isNextButtonEnabled: Bool - let onBack: () -> Void - let onNext: () -> Void - - static func noButtons() -> PointOfSaleFlowButtonConfiguration { - .init( - shouldShowBackButton: false, - shouldShowNextButton: false, - nextButtonTitle: "", - isNextButtonEnabled: false, - onBack: {}, - onNext: {} - ) - } - - static func doneOnly(onDone: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { - .init( - shouldShowBackButton: false, - shouldShowNextButton: true, - nextButtonTitle: Localization.doneButtonTitle, - isNextButtonEnabled: true, - onBack: {}, - onNext: onDone - ) - } - - static func closeAndRetry(onClose: @escaping () -> Void, - onRetry: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { - .init( - shouldShowBackButton: true, - shouldShowNextButton: true, - nextButtonTitle: Localization.retryButtonTitle, - isNextButtonEnabled: true, - onBack: onClose, - onNext: onRetry - ) - } - - static func disabledNext(onBack: @escaping () -> Void, - onNext: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { - .init( - shouldShowBackButton: true, - shouldShowNextButton: true, - nextButtonTitle: Localization.nextButtonTitle, - isNextButtonEnabled: false, - onBack: onBack, - onNext: onNext - ) - } -} - -// MARK: - Private Localization Extension -private extension PointOfSaleFlowButtonConfiguration { - enum Localization { - static let doneButtonTitle = NSLocalizedString( - "pos.flow.done.button.title", - value: "Done", - comment: "Title for the done button in a step by step flow view in POS" - ) - static let retryButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.retry.button.title", - value: "Retry", - comment: "Title for the retry button in a step by step flow view in POS" - ) - static let nextButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.next.button.title", - value: "Next", - comment: "Title for the next button in a step by step flow view in POS" - ) - } -} - // MARK: - Step Customization Protocol @available(iOS 17.0, *) protocol PointOfSaleBarcodeScannerStepCustomization { diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift index fa0067ca384..022fa94131a 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift @@ -1,29 +1,5 @@ import SwiftUI -// MARK: - Flow Buttons View -@available(iOS 17.0, *) -struct FlowButtonsView: View { - let buttonConfiguration: PointOfSaleFlowButtonConfiguration - - var body: some View { - HStack(spacing: POSSpacing.medium) { - if buttonConfiguration.shouldShowBackButton { - Button(Localization.backButtonTitle) { - buttonConfiguration.onBack() - } - .buttonStyle(POSOutlinedButtonStyle(size: .normal)) - } - if buttonConfiguration.shouldShowNextButton { - Button(buttonConfiguration.nextButtonTitle) { - buttonConfiguration.onNext() - } - .buttonStyle(POSFilledButtonStyle(size: .normal)) - .disabled(!buttonConfiguration.isNextButtonEnabled) - } - } - } -} - // MARK: - Scanner Selection View struct ScannerSelectionView: View { let options: [PointOfSaleBarcodeScannerSetupFlowOption] @@ -100,18 +76,6 @@ struct ScannerWelcomeView: View { } // MARK: - Private Localization Extensions - -@available(iOS 17.0, *) -private extension FlowButtonsView { - enum Localization { - static let backButtonTitle = NSLocalizedString( - "pos.barcodeScannerSetup.back.button.title", - value: "Back", - comment: "Title for the back button in barcode scanner setup navigation" - ) - } -} - private extension ScannerSelectionView { enum Localization { static let setupIntroMessage = NSLocalizedString( diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift new file mode 100644 index 00000000000..397560456ca --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift @@ -0,0 +1,112 @@ +import SwiftUI + +struct PointOfSaleFlowButtonsView: View { + let configuration: PointOfSaleFlowButtonConfiguration + + var body: some View { + HStack(spacing: POSSpacing.medium) { + if configuration.shouldShowBackButton { + Button(configuration.backButtonTitle) { + configuration.onBack() + } + .buttonStyle(POSOutlinedButtonStyle(size: .normal)) + } + if configuration.shouldShowNextButton { + Button(configuration.nextButtonTitle) { + configuration.onNext() + } + .buttonStyle(POSFilledButtonStyle(size: .normal)) + .disabled(!configuration.isNextButtonEnabled) + } + } + } +} + +private extension PointOfSaleFlowButtonsView { + enum Localization { + + } +} + +// MARK: - Button Configuration +struct PointOfSaleFlowButtonConfiguration { + let shouldShowBackButton: Bool + let shouldShowNextButton: Bool + let backButtonTitle = Localization.backButtonTitle + let nextButtonTitle: String + let isNextButtonEnabled: Bool + let onBack: () -> Void + let onNext: () -> Void + + static func noButtons() -> PointOfSaleFlowButtonConfiguration { + .init( + shouldShowBackButton: false, + shouldShowNextButton: false, + nextButtonTitle: "", + isNextButtonEnabled: false, + onBack: {}, + onNext: {} + ) + } + + static func doneOnly(onDone: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { + .init( + shouldShowBackButton: false, + shouldShowNextButton: true, + nextButtonTitle: Localization.doneButtonTitle, + isNextButtonEnabled: true, + onBack: {}, + onNext: onDone + ) + } + + static func closeAndRetry(onClose: @escaping () -> Void, + onRetry: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { + .init( + shouldShowBackButton: true, + shouldShowNextButton: true, + nextButtonTitle: Localization.retryButtonTitle, + isNextButtonEnabled: true, + onBack: onClose, + onNext: onRetry + ) + } + + static func disabledNext(onBack: @escaping () -> Void, + onNext: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { + .init( + shouldShowBackButton: true, + shouldShowNextButton: true, + nextButtonTitle: Localization.nextButtonTitle, + isNextButtonEnabled: false, + onBack: onBack, + onNext: onNext + ) + } +} + +// MARK: - Private Localization Extension +private extension PointOfSaleFlowButtonConfiguration { + enum Localization { + static let doneButtonTitle = NSLocalizedString( + "pos.flow.done.button.title", + value: "Done", + comment: "Title for the done button in a step by step flow view in POS" + ) + static let retryButtonTitle = NSLocalizedString( + "pos.flow.retry.button.title", + value: "Retry", + comment: "Title for the retry button in a step by step flow view in POS" + ) + static let nextButtonTitle = NSLocalizedString( + "pos.flow.next.button.title", + value: "Next", + comment: "Title for the next button in a step by step flow view in POS" + ) + static let backButtonTitle = NSLocalizedString( + "pos.flow.back.button.title", + value: "Back", + comment: "Title for the back button in barcode scanner setup navigation" + ) + } +} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index a27d0c030c3..4a85e1a4037 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -918,6 +918,7 @@ 20C3DB252E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */; }; 20C3DB262E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */; }; 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift */; }; + 20C3DB292E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB282E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift */; }; 20C6E7512CDE4AEA00CD124C /* ItemListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */; }; 20C909962D3151FA0013BCCF /* ItemListBaseItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C909952D3151FA0013BCCF /* ItemListBaseItem.swift */; }; 20CC1EDB2AFA8381006BD429 /* InPersonPaymentsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20CC1EDA2AFA8381006BD429 /* InPersonPaymentsMenu.swift */; }; @@ -4087,6 +4088,7 @@ 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupFlowManager.swift; sourceTree = ""; }; 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupModels.swift; sourceTree = ""; }; 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupViews.swift; sourceTree = ""; }; + 20C3DB282E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleFlowButtonsView.swift; sourceTree = ""; }; 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListState.swift; sourceTree = ""; }; 20C909952D3151FA0013BCCF /* ItemListBaseItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListBaseItem.swift; sourceTree = ""; }; 20CC1EDA2AFA8381006BD429 /* InPersonPaymentsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsMenu.swift; sourceTree = ""; }; @@ -8308,6 +8310,7 @@ 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */, 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */, 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */, + 20C3DB282E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift */, ); path = "Barcode Scanner Setup"; sourceTree = ""; @@ -15795,6 +15798,7 @@ CE6302432BAB431D00E3325C /* CustomerDetailView.swift in Sources */, EE45E29F2A381A2E0085F227 /* ProductDescriptionGenerationCelebrationViewModel.swift in Sources */, 02667A1A2ABDD44200C77B56 /* GiftCardCodeScannerViewController.swift in Sources */, + 20C3DB292E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift in Sources */, 57A25C7C25ACFAEC00A54A62 /* OrderFulfillmentNoticePresenter.swift in Sources */, B9E4364C287587D300883CFA /* FeatureAnnouncementCardView.swift in Sources */, 205E794B2C2051B5001BA266 /* PointOfSaleCardPresentPaymentTapSwipeInsertCardMessageView.swift in Sources */, From 4fc9582f518fdb4febd300c1490377ea478f9887 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:43:48 +0100 Subject: [PATCH 15/21] Rename ScannerSelectionView for specificity --- .../PointOfSaleBarcodeScannerSetup.swift | 2 +- .../PointOfSaleBarcodeScannerSetupViews.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift index 04e002359f3..39f3e9da729 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift @@ -49,7 +49,7 @@ struct PointOfSaleBarcodeScannerSetup: View { private var currentContent: some View { switch flowManager.currentState { case .scannerSelection: - ScannerSelectionView(options: scannerOptions) { scannerType in + PointOfSaleBarcodeScannerSetupSelectionView(options: scannerOptions) { scannerType in flowManager.selectScanner(scannerType) } case .setupFlow: diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift index 022fa94131a..99db1cab96b 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift @@ -1,7 +1,7 @@ import SwiftUI // MARK: - Scanner Selection View -struct ScannerSelectionView: View { +struct PointOfSaleBarcodeScannerSetupSelectionView: View { let options: [PointOfSaleBarcodeScannerSetupFlowOption] let onSelection: (PointOfSaleBarcodeScannerType) -> Void @@ -76,7 +76,7 @@ struct ScannerWelcomeView: View { } // MARK: - Private Localization Extensions -private extension ScannerSelectionView { +private extension PointOfSaleBarcodeScannerSetupSelectionView { enum Localization { static let setupIntroMessage = NSLocalizedString( "pos.barcodeScannerSetup.introMessage", From 7f4c1494d729a9ba5c3559116ebbc2607e391db9 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:45:27 +0100 Subject: [PATCH 16/21] Rename remaining views for specificity --- .../PointOfSaleBarcodeScannerSetupFlow.swift | 2 +- .../PointOfSaleBarcodeScannerSetupViews.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift index ea455fd9cff..18ad3c5111e 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -111,7 +111,7 @@ class PointOfSaleBarcodeScannerSetupFlow { private func createWelcomeStep(title: String) -> PointOfSaleBarcodeScannerSetupStep { PointOfSaleBarcodeScannerSetupStep( title: title, - content: { ScannerWelcomeView(title: title) }, + content: { PointOfSaleBarcodeScannerWelcomeView(title: title) }, customization: PointOfSaleBarcodeScannerWelcomeStepCustomization() ) } diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift index 99db1cab96b..df479b348d1 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift @@ -18,7 +18,7 @@ struct PointOfSaleBarcodeScannerSetupSelectionView: View { Button { onSelection(option.scannerType) } label: { - ScannerOptionView( + PointOfSaleBarcodeScannerOptionView( title: option.title, subtitle: option.subtitle ) @@ -31,7 +31,7 @@ struct PointOfSaleBarcodeScannerSetupSelectionView: View { } // MARK: - Scanner Option View -struct ScannerOptionView: View { +struct PointOfSaleBarcodeScannerOptionView: View { let title: String let subtitle: String @@ -57,7 +57,7 @@ struct ScannerOptionView: View { } // MARK: - Step Views -struct ScannerWelcomeView: View { +struct PointOfSaleBarcodeScannerWelcomeView: View { let title: String var body: some View { From 17799fb6a75fe5277c21b191454003b3e12b3dab Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:48:00 +0100 Subject: [PATCH 17/21] Remove unused localization --- .../Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift index 397560456ca..feac25f9bd3 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift @@ -22,12 +22,6 @@ struct PointOfSaleFlowButtonsView: View { } } -private extension PointOfSaleFlowButtonsView { - enum Localization { - - } -} - // MARK: - Button Configuration struct PointOfSaleFlowButtonConfiguration { let shouldShowBackButton: Bool From 2487604af3f895242136c8661a1a848b330d520d Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 10:53:18 +0100 Subject: [PATCH 18/21] Improve button configuration --- .../PointOfSaleFlowButtonsView.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift index feac25f9bd3..86688ed0758 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift @@ -26,7 +26,7 @@ struct PointOfSaleFlowButtonsView: View { struct PointOfSaleFlowButtonConfiguration { let shouldShowBackButton: Bool let shouldShowNextButton: Bool - let backButtonTitle = Localization.backButtonTitle + let backButtonTitle: String let nextButtonTitle: String let isNextButtonEnabled: Bool let onBack: () -> Void @@ -36,6 +36,7 @@ struct PointOfSaleFlowButtonConfiguration { .init( shouldShowBackButton: false, shouldShowNextButton: false, + backButtonTitle: "", nextButtonTitle: "", isNextButtonEnabled: false, onBack: {}, @@ -47,6 +48,7 @@ struct PointOfSaleFlowButtonConfiguration { .init( shouldShowBackButton: false, shouldShowNextButton: true, + backButtonTitle: "", nextButtonTitle: Localization.doneButtonTitle, isNextButtonEnabled: true, onBack: {}, @@ -59,6 +61,7 @@ struct PointOfSaleFlowButtonConfiguration { .init( shouldShowBackButton: true, shouldShowNextButton: true, + backButtonTitle: Localization.closeButtonTitle, nextButtonTitle: Localization.retryButtonTitle, isNextButtonEnabled: true, onBack: onClose, @@ -71,6 +74,7 @@ struct PointOfSaleFlowButtonConfiguration { .init( shouldShowBackButton: true, shouldShowNextButton: true, + backButtonTitle: Localization.backButtonTitle, nextButtonTitle: Localization.nextButtonTitle, isNextButtonEnabled: false, onBack: onBack, @@ -100,7 +104,12 @@ private extension PointOfSaleFlowButtonConfiguration { static let backButtonTitle = NSLocalizedString( "pos.flow.back.button.title", value: "Back", - comment: "Title for the back button in barcode scanner setup navigation" + comment: "Title for the back button in a step by step flow view in POS" + ) + static let closeButtonTitle = NSLocalizedString( + "pos.flow.close.button.title", + value: "Close", + comment: "Title for the back button in a step by step flow view in POS" ) } } From ebf97cf89407937856a568a581e0647f4a5ade1b Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 11:20:31 +0100 Subject: [PATCH 19/21] Simplify configuration of flow buttons --- .../PointOfSaleBarcodeScannerSetupFlow.swift | 53 +++----- .../PointOfSaleFlowButtonsView.swift | 121 +++++------------- 2 files changed, 47 insertions(+), 127 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift index 18ad3c5111e..af5a4900d37 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -28,14 +28,6 @@ class PointOfSaleBarcodeScannerSetupFlow { isComplete ? Localization.doneButtonTitle : Localization.nextButtonTitle } - var isNextButtonEnabled: Bool { - true - } - - var shouldShowBackButton: Bool { - true - } - func nextStep() { if currentStepIndex < steps.count - 1 { currentStepIndex += 1 @@ -61,23 +53,20 @@ class PointOfSaleBarcodeScannerSetupFlow { return .noButtons() } - // Use step customization if available - if let customization = step.customization { - return customization.customizeButtons(for: self) - } - // Default button configuration return PointOfSaleFlowButtonConfiguration( - shouldShowBackButton: shouldShowBackButton, - shouldShowNextButton: true, - nextButtonTitle: nextButtonTitle, - isNextButtonEnabled: isNextButtonEnabled, - onBack: { [weak self] in - self?.previousStep() - }, - onNext: { [weak self] in - self?.nextStep() - } + primaryButton: PointOfSaleFlowButtonConfiguration.ButtonConfig( + title: nextButtonTitle, + action: { [weak self] in + self?.nextStep() + } + ), + secondaryButton: PointOfSaleFlowButtonConfiguration.ButtonConfig( + title: Localization.backButtonTitle, + action: { [weak self] in + self?.previousStep() + } + ) ) } @@ -111,22 +100,11 @@ class PointOfSaleBarcodeScannerSetupFlow { private func createWelcomeStep(title: String) -> PointOfSaleBarcodeScannerSetupStep { PointOfSaleBarcodeScannerSetupStep( title: title, - content: { PointOfSaleBarcodeScannerWelcomeView(title: title) }, - customization: PointOfSaleBarcodeScannerWelcomeStepCustomization() + content: { PointOfSaleBarcodeScannerWelcomeView(title: title) } ) } } -// MARK: - Example Step Customizations -@available(iOS 17.0, *) -struct PointOfSaleBarcodeScannerWelcomeStepCustomization: PointOfSaleBarcodeScannerStepCustomization { - func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration { - return .doneOnly { - flow.nextStep() - } - } -} - // MARK: - Private Localization Extension @available(iOS 17.0, *) private extension PointOfSaleBarcodeScannerSetupFlow { @@ -141,5 +119,10 @@ private extension PointOfSaleBarcodeScannerSetupFlow { value: "Next", comment: "Title for the next button in barcode scanner setup navigation" ) + static let backButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.back.button.title", + value: "Back", + comment: "Title for the back button in barcode scanner setup navigation" + ) } } diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift index 86688ed0758..d5bc99b8ea8 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleFlowButtonsView.swift @@ -5,18 +5,20 @@ struct PointOfSaleFlowButtonsView: View { var body: some View { HStack(spacing: POSSpacing.medium) { - if configuration.shouldShowBackButton { - Button(configuration.backButtonTitle) { - configuration.onBack() + if let secondaryButton = configuration.secondaryButton { + Button(secondaryButton.title) { + secondaryButton.action() } .buttonStyle(POSOutlinedButtonStyle(size: .normal)) + .disabled(!secondaryButton.isEnabled) } - if configuration.shouldShowNextButton { - Button(configuration.nextButtonTitle) { - configuration.onNext() + + if let primaryButton = configuration.primaryButton { + Button(primaryButton.title) { + primaryButton.action() } .buttonStyle(POSFilledButtonStyle(size: .normal)) - .disabled(!configuration.isNextButtonEnabled) + .disabled(!primaryButton.isEnabled) } } } @@ -24,92 +26,27 @@ struct PointOfSaleFlowButtonsView: View { // MARK: - Button Configuration struct PointOfSaleFlowButtonConfiguration { - let shouldShowBackButton: Bool - let shouldShowNextButton: Bool - let backButtonTitle: String - let nextButtonTitle: String - let isNextButtonEnabled: Bool - let onBack: () -> Void - let onNext: () -> Void - - static func noButtons() -> PointOfSaleFlowButtonConfiguration { - .init( - shouldShowBackButton: false, - shouldShowNextButton: false, - backButtonTitle: "", - nextButtonTitle: "", - isNextButtonEnabled: false, - onBack: {}, - onNext: {} - ) - } - - static func doneOnly(onDone: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { - .init( - shouldShowBackButton: false, - shouldShowNextButton: true, - backButtonTitle: "", - nextButtonTitle: Localization.doneButtonTitle, - isNextButtonEnabled: true, - onBack: {}, - onNext: onDone - ) - } - - static func closeAndRetry(onClose: @escaping () -> Void, - onRetry: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { - .init( - shouldShowBackButton: true, - shouldShowNextButton: true, - backButtonTitle: Localization.closeButtonTitle, - nextButtonTitle: Localization.retryButtonTitle, - isNextButtonEnabled: true, - onBack: onClose, - onNext: onRetry - ) - } - - static func disabledNext(onBack: @escaping () -> Void, - onNext: @escaping () -> Void) -> PointOfSaleFlowButtonConfiguration { - .init( - shouldShowBackButton: true, - shouldShowNextButton: true, - backButtonTitle: Localization.backButtonTitle, - nextButtonTitle: Localization.nextButtonTitle, - isNextButtonEnabled: false, - onBack: onBack, - onNext: onNext - ) + let primaryButton: ButtonConfig? + let secondaryButton: ButtonConfig? + + struct ButtonConfig { + let title: String + let isEnabled: Bool + let action: () -> Void + + init( + title: String, + isEnabled: Bool = true, + action: @escaping () -> Void, + ) { + self.title = title + self.isEnabled = isEnabled + self.action = action + } } -} -// MARK: - Private Localization Extension -private extension PointOfSaleFlowButtonConfiguration { - enum Localization { - static let doneButtonTitle = NSLocalizedString( - "pos.flow.done.button.title", - value: "Done", - comment: "Title for the done button in a step by step flow view in POS" - ) - static let retryButtonTitle = NSLocalizedString( - "pos.flow.retry.button.title", - value: "Retry", - comment: "Title for the retry button in a step by step flow view in POS" - ) - static let nextButtonTitle = NSLocalizedString( - "pos.flow.next.button.title", - value: "Next", - comment: "Title for the next button in a step by step flow view in POS" - ) - static let backButtonTitle = NSLocalizedString( - "pos.flow.back.button.title", - value: "Back", - comment: "Title for the back button in a step by step flow view in POS" - ) - static let closeButtonTitle = NSLocalizedString( - "pos.flow.close.button.title", - value: "Close", - comment: "Title for the back button in a step by step flow view in POS" - ) + // MARK: - Convenience Initializers + static func noButtons() -> PointOfSaleFlowButtonConfiguration { + .init(primaryButton: nil, secondaryButton: nil) } } From e39dd9fd932e249a03ecd9d20e60b87f8751df54 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Wed, 9 Jul 2025 11:40:24 +0100 Subject: [PATCH 20/21] Button customization support readded --- .../PointOfSaleBarcodeScannerSetupFlow.swift | 30 ++++++++++++++++++- ...PointOfSaleBarcodeScannerSetupModels.swift | 10 +++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift index af5a4900d37..94726b169a5 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -53,6 +53,11 @@ class PointOfSaleBarcodeScannerSetupFlow { return .noButtons() } + // Use step customization if available + if let customization = step.buttonCustomization { + return customization.customizeButtons(for: self) + } + // Default button configuration return PointOfSaleFlowButtonConfiguration( primaryButton: PointOfSaleFlowButtonConfiguration.ButtonConfig( @@ -100,7 +105,30 @@ class PointOfSaleBarcodeScannerSetupFlow { private func createWelcomeStep(title: String) -> PointOfSaleBarcodeScannerSetupStep { PointOfSaleBarcodeScannerSetupStep( title: title, - content: { PointOfSaleBarcodeScannerWelcomeView(title: title) } + content: { PointOfSaleBarcodeScannerWelcomeView(title: title) }, + buttonCustomization: PointOfSaleBarcodeScannerWelcomeButtonCustomization() + ) + } +} + +// MARK: - Button Customizations +@available(iOS 17.0, *) +struct PointOfSaleBarcodeScannerWelcomeButtonCustomization: PointOfSaleBarcodeScannerButtonCustomization { + func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration { + return PointOfSaleFlowButtonConfiguration( + primaryButton: PointOfSaleFlowButtonConfiguration.ButtonConfig( + title: Localization.doneButtonTitle, + action: { flow.nextStep() } + ), + secondaryButton: nil + ) + } + + private enum Localization { + static let doneButtonTitle = NSLocalizedString( + "pos.barcodeScannerSetup.done.button.title", + value: "Done", + comment: "Title for the done button in barcode scanner setup navigation" ) } } diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift index a9e1e5dc0f5..12a1ce4554d 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -21,9 +21,9 @@ enum PointOfSaleBarcodeScannerSetupFlowState { case setupFlow(PointOfSaleBarcodeScannerType) } -// MARK: - Step Customization Protocol +// MARK: - Button Customization Protocol @available(iOS 17.0, *) -protocol PointOfSaleBarcodeScannerStepCustomization { +protocol PointOfSaleBarcodeScannerButtonCustomization { func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration } @@ -32,15 +32,15 @@ protocol PointOfSaleBarcodeScannerStepCustomization { struct PointOfSaleBarcodeScannerSetupStep { let title: String let content: any View - let customization: PointOfSaleBarcodeScannerStepCustomization? + let buttonCustomization: PointOfSaleBarcodeScannerButtonCustomization? init( title: String, @ViewBuilder content: () -> any View, - customization: PointOfSaleBarcodeScannerStepCustomization? = nil + buttonCustomization: PointOfSaleBarcodeScannerButtonCustomization? = nil ) { self.title = title self.content = content() - self.customization = customization + self.buttonCustomization = buttonCustomization } } From 5e405e7402ee96677fa977f1c3a3cf70e4c34f53 Mon Sep 17 00:00:00 2001 From: Josh Heald Date: Thu, 10 Jul 2025 08:54:13 +0100 Subject: [PATCH 21/21] Ensure content changes when moving step --- .../PointOfSaleBarcodeScannerSetupFlow.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift index 94726b169a5..9feb543c75d 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -2,6 +2,7 @@ import SwiftUI // MARK: - Point of Sale Barcode Scanner Setup Flow @available(iOS 17.0, *) +@Observable class PointOfSaleBarcodeScannerSetupFlow { private let scannerType: PointOfSaleBarcodeScannerType private let onComplete: () -> Void