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/PointOfSaleBarcodeScannerSetup.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift new file mode 100644 index 00000000000..39f3e9da729 --- /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 + PointOfSaleFlowButtonsView(configuration: 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: + PointOfSaleBarcodeScannerSetupSelectionView(options: scannerOptions) { scannerType in + flowManager.selectScanner(scannerType) + } + case .setupFlow: + if let step = flowManager.getCurrentStep() { + AnyView(step.content) + } + } + } + + private var scannerOptions: [PointOfSaleBarcodeScannerSetupFlowOption] { + [ + PointOfSaleBarcodeScannerSetupFlowOption( + title: Localization.socketS720Title, + subtitle: Localization.socketS720Subtitle, + scannerType: .socketS720 + ), + PointOfSaleBarcodeScannerSetupFlowOption( + title: Localization.starBSH20BTitle, + subtitle: Localization.starBSH20BSubtitle, + scannerType: .starBSH20B + ), + PointOfSaleBarcodeScannerSetupFlowOption( + title: Localization.tbcScannerTitle, + subtitle: Localization.tbcScannerSubtitle, + scannerType: .tbcScanner + ), + PointOfSaleBarcodeScannerSetupFlowOption( + 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/PointOfSaleBarcodeScannerSetupFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift new file mode 100644 index 00000000000..18ad3c5111e --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -0,0 +1,145 @@ +import SwiftUI + +// MARK: - Point of Sale Barcode Scanner Setup Flow +@available(iOS 17.0, *) +class PointOfSaleBarcodeScannerSetupFlow { + private let scannerType: PointOfSaleBarcodeScannerType + private let onComplete: () -> Void + private let onBackToSelection: () -> Void + private var currentStepIndex: Int = 0 + + init(scannerType: PointOfSaleBarcodeScannerType, + 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() -> PointOfSaleFlowButtonConfiguration { + 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 PointOfSaleFlowButtonConfiguration( + 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: { PointOfSaleBarcodeScannerWelcomeView(title: title) }, + customization: PointOfSaleBarcodeScannerWelcomeStepCustomization() + ) + } +} + +// 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 { + 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" + ) + } +} 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..f4bff61f6ab --- /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: PointOfSaleBarcodeScannerSetupFlow? + + init(isPresented: Binding) { + self._isPresented = isPresented + } + + func selectScanner(_ scannerType: PointOfSaleBarcodeScannerType) { + currentFlow = PointOfSaleBarcodeScannerSetupFlow(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: PointOfSaleFlowButtonConfiguration { + switch currentState { + case .scannerSelection: + return .noButtons() + case .setupFlow: + guard let flow = currentFlow else { + return .noButtons() + } + + return flow.getButtonConfiguration() + } + } +} 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..a9e1e5dc0f5 --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -0,0 +1,46 @@ +import SwiftUI + +// MARK: - Data Models +struct PointOfSaleBarcodeScannerSetupFlowOption: Identifiable { + let id = UUID() + let title: String + let subtitle: String + let scannerType: PointOfSaleBarcodeScannerType +} + +enum PointOfSaleBarcodeScannerType { + case socketS720 + case starBSH20B + case tbcScanner + case other +} + +// MARK: - Flow State +enum PointOfSaleBarcodeScannerSetupFlowState { + case scannerSelection + case setupFlow(PointOfSaleBarcodeScannerType) +} + +// MARK: - Step Customization Protocol +@available(iOS 17.0, *) +protocol PointOfSaleBarcodeScannerStepCustomization { + func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration +} + +// 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 + } +} 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..df479b348d1 --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupViews.swift @@ -0,0 +1,87 @@ +import SwiftUI + +// MARK: - Scanner Selection View +struct PointOfSaleBarcodeScannerSetupSelectionView: View { + let options: [PointOfSaleBarcodeScannerSetupFlowOption] + let onSelection: (PointOfSaleBarcodeScannerType) -> 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: { + PointOfSaleBarcodeScannerOptionView( + title: option.title, + subtitle: option.subtitle + ) + } + .buttonStyle(PlainButtonStyle()) + } + } + } + } +} + +// MARK: - Scanner Option View +struct PointOfSaleBarcodeScannerOptionView: 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 PointOfSaleBarcodeScannerWelcomeView: 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 +private extension PointOfSaleBarcodeScannerSetupSelectionView { + 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/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/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 18270ca10cd..00000000000 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerSetUpFlow.swift +++ /dev/null @@ -1,227 +0,0 @@ -import SwiftUI - -// MARK: - Data Models -struct ScannerOption: Identifiable { - let id = UUID() - let title: String - let subtitle: String - let destination: SetupDestination -} - -enum SetupDestination { - case socketS720 - case starBSH20B - case tbcScanner - case other -} - -@available(iOS 17.0, *) -struct PointOfSaleBarcodeScannerSetUpFlow: View { - @Binding var isPresented: Bool - - 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() - } - .scrollVerticallyIfNeeded() - } - .toolbar(.hidden, for: .navigationBar) - .padding(POSPadding.xxLarge) - } - .background(Color.posSurfaceBright) - .containerRelativeFrame([.horizontal, .vertical]) { length, _ in - max(length * 0.75, Constants.modalFrameMaxSmallDimension) - } - .onAppear { - ServiceLocator.analytics.track(.pointOfSaleBarcodeScannerSetupFlowShown) - } - } - - private var scannerOptions: [ScannerOption] { - [ - ScannerOption( - title: Localization.socketS720Title, - subtitle: Localization.socketS720Subtitle, - destination: .socketS720 - ), - ScannerOption( - title: Localization.starBSH20BTitle, - subtitle: Localization.starBSH20BSubtitle, - destination: .starBSH20B - ), - ScannerOption( - title: Localization.tbcScannerTitle, - subtitle: Localization.tbcScannerSubtitle, - destination: .tbcScanner - ), - ScannerOption( - title: Localization.otherTitle, - subtitle: Localization.otherSubtitle, - destination: .other - ) - ] - } -} - -struct ScannerSelectionView: View { - let options: [ScannerOption] - @Binding var isPresented: Bool - - 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 - NavigationLink(destination: destinationView(for: option.destination)) { - ScannerOptionView( - title: option.title, - subtitle: option.subtitle - ) - } - .buttonStyle(PlainButtonStyle()) - } - } - } - } - - @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) - } - } -} - -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)) - } -} - -struct BarcodeScannerInformationView: View { - @Binding var isPresented: Bool - - var body: some View { - VStack(spacing: POSSpacing.xxLarge) { - PointOfSaleModalHeader(isPresented: $isPresented, - title: .constant(AttributedString(PointOfSaleBarcodeScannerInformationModal.Localization.barcodeInfoHeading))) - VStack { - BarcodeScannerInformationContent() - Spacer() - } - .scrollVerticallyIfNeeded() - } - .toolbar(.hidden, for: .navigationBar) - } -} - - -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" - ) -} - -@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..4a85e1a4037 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -912,8 +912,13 @@ 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 /* 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 */; }; @@ -4077,8 +4082,13 @@ 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 /* 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 = ""; }; + 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 = ""; }; @@ -7092,6 +7102,7 @@ 026826A12BF59DED0036F959 /* Presentation */ = { isa = PBXGroup; children = ( + 20C3DB222E1E69CF00CF7D3B /* Barcode Scanner Setup */, 20D557572DF9D57800D9EC8B /* Barcode Scanning */, 016A77672D9D24A30004FCD6 /* Coupons */, 026A50262D2F6BBF002C42C2 /* Infinite Scroll */, @@ -7112,7 +7123,6 @@ 01435CF72DFC2CE800C0279B /* PointOfSaleInformationModal.swift */, 20C3CC3B2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift */, 014371262DFC8E2100C0279B /* PointOfSaleBarcodeScannerInformationModal.swift */, - 20C3C8812E1D11F500CF7D3B /* PointOfSaleBarcodeScannerSetUpFlow.swift */, 68C53CBD2C1FE59B00C6D80B /* ItemListView.swift */, 026826A32BF59DF60036F959 /* CartView.swift */, 026826A22BF59DF60036F959 /* ItemRowView.swift */, @@ -8292,6 +8302,19 @@ path = AddOrderComponentsSection; sourceTree = ""; }; + 20C3DB222E1E69CF00CF7D3B /* Barcode Scanner Setup */ = { + isa = PBXGroup; + children = ( + 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift */, + 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */, + 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */, + 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */, + 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */, + 20C3DB282E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift */, + ); + path = "Barcode Scanner Setup"; + sourceTree = ""; + }; 20CCBF1F2B0E159D003102E6 /* Deposits Overview */ = { isa = PBXGroup; children = ( @@ -15775,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 */, @@ -15919,6 +15943,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 /* PointOfSaleBarcodeScannerSetupFlow.swift in Sources */, 0260B1B12805321B00FCFE8C /* OrderDetailsPaymentAlertsProtocol.swift in Sources */, 4556ED38270645A6005CBC0D /* ShippingLabelCarrierSectionHeader.swift in Sources */, 267C01CF29E89E1700FCC97B /* StorePlanSynchronizer.swift in Sources */, @@ -16079,7 +16108,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 */,