diff --git a/Modules/Package.swift b/Modules/Package.swift index 35678a5058b..42a3c1d729e 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -179,7 +179,10 @@ let package = Package( ), .target( name: "WooFoundation", - dependencies: ["WooFoundationCore"] + dependencies: [ + "WooFoundationCore", + .product(name: "Kingfisher", package: "Kingfisher") + ] ), .target( name: "WooFoundationCore", diff --git a/WooCommerce/Classes/ViewRelated/Products/ProductSelector/ProductImageThumbnail.swift b/Modules/Sources/WooFoundation/UI Components/ProductImageThumbnail.swift similarity index 60% rename from WooCommerce/Classes/ViewRelated/Products/ProductSelector/ProductImageThumbnail.swift rename to Modules/Sources/WooFoundation/UI Components/ProductImageThumbnail.swift index eb809ea78e2..4d73afcd0db 100644 --- a/WooCommerce/Classes/ViewRelated/Products/ProductSelector/ProductImageThumbnail.swift +++ b/Modules/Sources/WooFoundation/UI Components/ProductImageThumbnail.swift @@ -1,7 +1,7 @@ import SwiftUI import Kingfisher -struct ProductImageThumbnail: View { +public struct ProductImageThumbnail: View { private let productImageURL: URL? private let productImageSize: CGFloat private let scale: CGFloat @@ -25,13 +25,13 @@ struct ProductImageThumbnail: View { ) } - init(productImageURL: URL?, - productImageSize: CGFloat, - scale: CGFloat, - productImageCornerRadius: CGFloat = 0, - foregroundColor: Color, - cachesOriginalImage: Bool = false, - @ViewBuilder placeholder: () -> Placeholder) { + public init(productImageURL: URL?, + productImageSize: CGFloat, + scale: CGFloat, + productImageCornerRadius: CGFloat = 0, + foregroundColor: Color, + cachesOriginalImage: Bool = false, + @ViewBuilder placeholder: () -> Placeholder) { self.productImageURL = productImageURL self.productImageSize = productImageSize self.scale = scale @@ -41,7 +41,7 @@ struct ProductImageThumbnail: View { self.placeholder = placeholder() } - var body: some View { + public var body: some View { KFImage .url(productImageURL) .cacheOriginalImage(cachesOriginalImage) @@ -57,22 +57,3 @@ struct ProductImageThumbnail: View { .accessibilityHidden(true) } } - -// Convenience initializer that maintains the default behavior. -extension ProductImageThumbnail where Placeholder == Image { - init(productImageURL: URL?, - productImageSize: CGFloat, - scale: CGFloat, - productImageCornerRadius: CGFloat = 0, - foregroundColor: Color, - cachesOriginalImage: Bool = false) { - self.init(productImageURL: productImageURL, - productImageSize: productImageSize, - scale: scale, - productImageCornerRadius: productImageCornerRadius, - foregroundColor: foregroundColor, - cachesOriginalImage: cachesOriginalImage) { - Image(uiImage: .productPlaceholderImage) - } - } -} diff --git a/WooCommerce/Classes/Extensions/ProductImageThumbnail+Extensions.swift b/WooCommerce/Classes/Extensions/ProductImageThumbnail+Extensions.swift new file mode 100644 index 00000000000..9fe4f7e4103 --- /dev/null +++ b/WooCommerce/Classes/Extensions/ProductImageThumbnail+Extensions.swift @@ -0,0 +1,21 @@ +import SwiftUI +import struct WooFoundation.ProductImageThumbnail + +// Convenience initializer that maintains the default behavior. +extension ProductImageThumbnail where Placeholder == Image { + init(productImageURL: URL?, + productImageSize: CGFloat, + scale: CGFloat, + productImageCornerRadius: CGFloat = 0, + foregroundColor: Color, + cachesOriginalImage: Bool = false) { + self.init(productImageURL: productImageURL, + productImageSize: productImageSize, + scale: scale, + productImageCornerRadius: productImageCornerRadius, + foregroundColor: foregroundColor, + cachesOriginalImage: cachesOriginalImage) { + Image(uiImage: .productPlaceholderImage) + } + } +} diff --git a/WooCommerce/Classes/POS/Adaptors/POSServiceLocatorAdaptor.swift b/WooCommerce/Classes/POS/Adaptors/POSServiceLocatorAdaptor.swift index 8a0fc72d466..5eaf6d3fa7c 100644 --- a/WooCommerce/Classes/POS/Adaptors/POSServiceLocatorAdaptor.swift +++ b/WooCommerce/Classes/POS/Adaptors/POSServiceLocatorAdaptor.swift @@ -5,7 +5,6 @@ import Yosemite import protocol Experiments.FeatureFlagService import enum Experiments.FeatureFlag import protocol Storage.StorageManagerType - final class POSServiceLocatorAdaptor: POSDependencyProviding { var analytics: POSAnalyticsProviding { POSAnalyticsAdaptor() @@ -84,4 +83,36 @@ private struct POSExternalViewAdaptor: POSExternalViewProviding { defaultSite: ServiceLocator.stores.sessionManager.defaultSite)) ) } + + func createFormattableAmountTextField(preset: Decimal?, + onSubmit: @escaping () -> Void, + onChange: @escaping (String) -> Void) -> AnyView { + AnyView(POSFormattableAmountTextFieldAdaptor(preset: preset, onSubmit: onSubmit, onChange: onChange)) + } + + func createCouponCreationView(discountType: Coupon.DiscountType, + showTypeSelection: Binding, + onSuccess: @escaping (Coupon) -> Void, + dismissHandler: @escaping () -> Void, + onDisappear: @escaping () -> Void) -> AnyView { + AnyView(POSCouponCreationViewAdaptor( + discountType: discountType, + showTypeSelection: showTypeSelection, + onSuccess: onSuccess, + dismissHandler: dismissHandler, + onDisappear: onDisappear + )) + } + + func createDiscountTypeSelectionSheet(isPresented: Binding, + title: String, + cancelButtonTitle: String, + onSelection: @escaping (Coupon.DiscountType) -> Void) -> AnyView { + AnyView(POSDiscountTypeSelectionSheetAdaptor( + isPresented: isPresented, + title: title, + cancelButtonTitle: cancelButtonTitle, + onSelection: onSelection + )) + } } diff --git a/WooCommerce/Classes/POS/Adaptors/View Adaptors/POSCouponCreationSheetAdaptor.swift b/WooCommerce/Classes/POS/Adaptors/View Adaptors/POSCouponCreationSheetAdaptor.swift new file mode 100644 index 00000000000..1830913d5de --- /dev/null +++ b/WooCommerce/Classes/POS/Adaptors/View Adaptors/POSCouponCreationSheetAdaptor.swift @@ -0,0 +1,74 @@ +import SwiftUI +import struct Yosemite.Coupon + +/// Provide access to AddEditCouponView and ViewModel for POS without making it as an explicit type dependency +/// AddEditCouponView cannot be easily moved and reused in a shared module due to multiple dependencies +/// This is used as a workaround to enable POS modularization without requiring a larger refactoring effort +/// + +struct POSCouponCreationViewAdaptor: View { + @StateObject private var viewModel: AddEditCouponViewModel + @Binding private var showTypeSelection: Bool + private let dismissHandler: () -> Void + private let onDisappear: () -> Void + + init(discountType: Coupon.DiscountType, + showTypeSelection: Binding, + onSuccess: @escaping (Coupon) -> Void, + dismissHandler: @escaping () -> Void, + onDisappear: @escaping () -> Void) { + _showTypeSelection = showTypeSelection + self.dismissHandler = dismissHandler + self.onDisappear = onDisappear + _viewModel = StateObject(wrappedValue: AddEditCouponViewModel( + discountType: discountType, + onSuccess: onSuccess + )) + } + + var body: some View { + var view = AddEditCoupon(viewModel) + view.dismissHandler = dismissHandler + view.onDisappear = onDisappear + view.discountTypeHandler = { _ in + showTypeSelection = true + } + + return view + .interactiveDismissDisabled() + } +} + +struct POSDiscountTypeSelectionSheetAdaptor: View { + @Binding var isPresented: Bool + let title: String + let cancelButtonTitle: String + let onSelection: (Coupon.DiscountType) -> Void + + var body: some View { + let command = DiscountTypeBottomSheetListSelectorCommand(selected: nil) { type in + onSelection(type) + } + + NavigationView { + BottomSheetListSelector( + viewProperties: BottomSheetListSelectorViewProperties(), + command: command, + onDismiss: { _ in + isPresented = false + } + ) + .navigationBarTitleDisplayMode(.large) + .navigationTitle(title) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button(cancelButtonTitle) { + isPresented = false + } + } + } + } + .navigationViewStyle(.stack) + .interactiveDismissDisabled() + } +} diff --git a/WooCommerce/Classes/POS/Adaptors/View Adaptors/POSFormattableAmountTextFieldAdaptor.swift b/WooCommerce/Classes/POS/Adaptors/View Adaptors/POSFormattableAmountTextFieldAdaptor.swift new file mode 100644 index 00000000000..1debbad22ba --- /dev/null +++ b/WooCommerce/Classes/POS/Adaptors/View Adaptors/POSFormattableAmountTextFieldAdaptor.swift @@ -0,0 +1,40 @@ +import SwiftUI + +/// Provide access to FormattableAmountTextField and ViewModel for POS without making it as an explicit type dependency +/// FormattableAmountTextField cannot be easily moved and reused in a shared module due to multiple dependencies +/// This is used as a workaround to enable POS modularization without requiring a larger refactoring effort +/// +struct POSFormattableAmountTextFieldAdaptor: View { + @StateObject private var textFieldViewModel: FormattableAmountTextFieldViewModel + let preset: Decimal? + let onSubmit: () -> Void + let onChange: (String) -> Void + + init(preset: Decimal?, onSubmit: @escaping () -> Void, onChange: @escaping (String) -> Void) { + self._textFieldViewModel = StateObject(wrappedValue: FormattableAmountTextFieldViewModel( + size: .extraLarge, + locale: Locale.autoupdatingCurrent, + storeCurrencySettings: ServiceLocator.currencySettings, + allowNegativeNumber: false) + ) + self.preset = preset + self.onSubmit = onSubmit + self.onChange = onChange + } + + var body: some View { + FormattableAmountTextField(viewModel: textFieldViewModel, style: .pos) + .dynamicTypeSize(...DynamicTypeSize.accessibility1) + .onSubmit { + onSubmit() + } + .onChange(of: textFieldViewModel.amount) { _, newValue in + onChange(newValue) + } + .onAppear { + if let preset { + textFieldViewModel.presetAmount(preset) + } + } + } +} diff --git a/WooCommerce/Classes/POS/Presentation/Coupons/POSCouponCreationSheet.swift b/WooCommerce/Classes/POS/Presentation/Coupons/POSCouponCreationSheet.swift index 190ae57a85d..09afced4a69 100644 --- a/WooCommerce/Classes/POS/Presentation/Coupons/POSCouponCreationSheet.swift +++ b/WooCommerce/Classes/POS/Presentation/Coupons/POSCouponCreationSheet.swift @@ -16,6 +16,7 @@ private struct POSCouponCreationSheetModifier: ViewModifier { @Binding var isPresented: Bool let onSuccess: (POSItem) -> Void + @Environment(\.posExternalViews) private var externalViews @State private var selectedType: POSCouponDiscountType? @State private var showCouponSelectionSheet: Bool = false @State private var addedCouponItem: POSItem? @@ -23,7 +24,7 @@ private struct POSCouponCreationSheetModifier: ViewModifier { func body(content: Content) -> some View { content .posSheet(item: $selectedType) { (posDiscountType: POSCouponDiscountType) in - POSCouponCreationView( + externalViews.createCouponCreationView( discountType: posDiscountType.discountType, showTypeSelection: $showCouponSelectionSheet, onSuccess: { coupon in @@ -40,81 +41,26 @@ private struct POSCouponCreationSheetModifier: ViewModifier { } } ) - } - .discountTypeSelectionSheet(isPresented: $isPresented) { type in - selectedType = type - } - } -} - -private struct POSCouponCreationView: View { - @StateObject private var viewModel: AddEditCouponViewModel - @Binding var showTypeSelection: Bool - let dismissHandler: () -> Void - let onDisappear: () -> Void - - init(discountType: Coupon.DiscountType, - showTypeSelection: Binding, - onSuccess: @escaping (Coupon) -> Void, - dismissHandler: @escaping () -> Void, - onDisappear: @escaping () -> Void) { - _showTypeSelection = showTypeSelection - self.dismissHandler = dismissHandler - self.onDisappear = onDisappear - _viewModel = StateObject(wrappedValue: AddEditCouponViewModel( - discountType: discountType, - onSuccess: onSuccess - )) - } - - var body: some View { - var view = AddEditCoupon(viewModel) - view.dismissHandler = dismissHandler - view.onDisappear = onDisappear - view.discountTypeHandler = { _ in - showTypeSelection = true - } - - return view - .interactiveDismissDisabled() - .discountTypeSelectionSheet(isPresented: $showTypeSelection) { type in - showTypeSelection = false - viewModel.discountType = type.discountType - } - } -} - -private extension View { - func discountTypeSelectionSheet( - isPresented: Binding, - onSelection: @escaping (POSCouponDiscountType) -> Void - ) -> some View { - posSheet(isPresented: isPresented) { - let command = DiscountTypeBottomSheetListSelectorCommand(selected: nil) { type in - onSelection(.init(discountType: type)) - } - - NavigationView { - BottomSheetListSelector( - viewProperties: BottomSheetListSelectorViewProperties(), - command: command, - onDismiss: { _ in - isPresented.wrappedValue = false - } - ) - .navigationBarTitleDisplayMode(.large) - .navigationTitle(Localization.selectCouponTypeTitle) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button(Localization.selectCouponCancelButtonTitle) { - isPresented.wrappedValue = false - } + .posSheet(isPresented: $showCouponSelectionSheet) { + externalViews.createDiscountTypeSelectionSheet( + isPresented: $showCouponSelectionSheet, + title: Localization.selectCouponTypeTitle, + cancelButtonTitle: Localization.selectCouponCancelButtonTitle + ) { type in + showCouponSelectionSheet = false + selectedType = POSCouponDiscountType(discountType: type) } } } - .navigationViewStyle(.stack) - .interactiveDismissDisabled() - } + .posSheet(isPresented: $isPresented) { + externalViews.createDiscountTypeSelectionSheet( + isPresented: $isPresented, + title: Localization.selectCouponTypeTitle, + cancelButtonTitle: Localization.selectCouponCancelButtonTitle + ) { type in + selectedType = POSCouponDiscountType(discountType: type) + } + } } } diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift index 708f09900ed..d2941a518f6 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift @@ -3,8 +3,8 @@ import Combine import WooFoundation struct PointOfSaleCollectCashView: View { - @Environment(\.dynamicTypeSize) var dynamicTypeSize - @Environment(\.posAnalytics) var analytics + @Environment(\.posAnalytics) private var analytics + @Environment(\.posExternalViews) private var externalViews @Environment(\.floatingControlAreaSize) private var floatingControlAreaSize: CGSize @Environment(PointOfSaleAggregateModel.self) private var posModel @FocusState private var isTextFieldFocused: Bool @@ -32,13 +32,7 @@ struct PointOfSaleCollectCashView: View { isLoading: isLoading) } - @StateObject private var textFieldViewModel: FormattableAmountTextFieldViewModel - init(orderTotal: String, currencySettings: CurrencySettings) { - self._textFieldViewModel = StateObject(wrappedValue: FormattableAmountTextFieldViewModel(size: .extraLarge, - locale: Locale.autoupdatingCurrent, - storeCurrencySettings: currencySettings, - allowNegativeNumber: false)) self.viewHelper = CollectCashViewHelper(currencySettings: currencySettings) self.orderTotal = orderTotal } @@ -61,18 +55,19 @@ struct PointOfSaleCollectCashView: View { Spacer() VStack(alignment: .center, spacing: conditionalPadding(POSSpacing.xSmall)) { - FormattableAmountTextField(viewModel: textFieldViewModel, style: .pos) - .focused($isTextFieldFocused) - .dynamicTypeSize(...DynamicTypeSize.accessibility1) - .onSubmit { + externalViews.createFormattableAmountTextField( + preset: viewHelper.parseCurrency(orderTotal), + onSubmit: { Task { @MainActor in await submitCashAmount() } - } - .onChange(of: textFieldViewModel.amount) { _, newValue in + }, + onChange: { newValue in textFieldAmountInput = newValue updateChangeDueMessage() } + ) + .focused($isTextFieldFocused) if let changeDue = changeDueMessage { Text(changeDue) @@ -124,7 +119,7 @@ struct PointOfSaleCollectCashView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) .onAppear { - prefillOrderTotal() + isTextFieldFocused = true } } @@ -153,13 +148,6 @@ private extension PointOfSaleCollectCashView { orderTotal: orderTotal, textFieldAmountInput: textFieldAmountInput) } - - private func prefillOrderTotal() { - if let orderDecimal = viewHelper.parseCurrency(orderTotal) { - textFieldViewModel.presetAmount(orderDecimal) - } - isTextFieldFocused = true - } } private extension PointOfSaleCollectCashView { diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSItemImageView.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSItemImageView.swift index 34522fddfd3..8ddf1eb6cac 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSItemImageView.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSItemImageView.swift @@ -1,4 +1,5 @@ import SwiftUI +import struct WooFoundation.ProductImageThumbnail enum POSItemImageState { case normal diff --git a/WooCommerce/Classes/POS/Protocols/POSDependencyProviding.swift b/WooCommerce/Classes/POS/Protocols/POSDependencyProviding.swift index 931edc43f32..095c0efd186 100644 --- a/WooCommerce/Classes/POS/Protocols/POSDependencyProviding.swift +++ b/WooCommerce/Classes/POS/Protocols/POSDependencyProviding.swift @@ -2,6 +2,8 @@ import SwiftUI import WooFoundationCore import WooFoundation import enum Experiments.FeatureFlag +import struct Yosemite.Coupon +import enum Yosemite.POSItem /// POSDepenencyProviding is part of the POS entry point that defines the external dependencies from the Woo app that POS depends on @@ -39,9 +41,24 @@ protocol POSExternalNavigationProviding { func navigateToCreateOrder() } -/// Protocol that provides external view creation capabilities for POS +/// Protocol that provides access to complex Woo application views that depend on a lot of Woo app target dependencies +/// and cannot be easily moved and reused in a shared module +/// This is used as a workaround to enable POS modularization without requiring a larger refactoring effort +/// protocol POSExternalViewProviding { func createSupportFormView(isPresented: Binding, sourceTag: String) -> AnyView + func createFormattableAmountTextField(preset: Decimal?, + onSubmit: @escaping () -> Void, + onChange: @escaping (String) -> Void) -> AnyView + func createCouponCreationView(discountType: Coupon.DiscountType, + showTypeSelection: Binding, + onSuccess: @escaping (Coupon) -> Void, + dismissHandler: @escaping () -> Void, + onDisappear: @escaping () -> Void) -> AnyView + func createDiscountTypeSelectionSheet(isPresented: Binding, + title: String, + cancelButtonTitle: String, + onSelection: @escaping (Coupon.DiscountType) -> Void) -> AnyView } /// Main protocol that combines all POS dependency providers diff --git a/WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift b/WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift index 8c708845c7c..7dab8f52bb1 100644 --- a/WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift +++ b/WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift @@ -4,6 +4,8 @@ import WooFoundation import Experiments import protocol Yosemite.Action import struct Yosemite.Site +import enum Yosemite.POSItem +import struct Yosemite.Coupon struct SiteTimezoneKey: EnvironmentKey { static let defaultValue: TimeZone = .current @@ -122,5 +124,23 @@ struct EmptyPOSAnalytics: POSAnalyticsProviding { struct EmptyPOSExternalView: POSExternalViewProviding { func createSupportFormView(isPresented: Binding, sourceTag: String) -> AnyView { AnyView(EmptyView()) } + func createFormattableAmountTextField(preset: Decimal?, + onSubmit: @escaping () -> Void, + onChange: @escaping (String) -> Void) -> AnyView { + AnyView(EmptyView()) + } + func createCouponCreationView(discountType: Coupon.DiscountType, + showTypeSelection: Binding, + onSuccess: @escaping (Coupon) -> Void, + dismissHandler: @escaping () -> Void, + onDisappear: @escaping () -> Void) -> AnyView { + AnyView(EmptyView()) + } + func createDiscountTypeSelectionSheet(isPresented: Binding, + title: String, + cancelButtonTitle: String, + onSelection: @escaping (Coupon.DiscountType) -> Void) -> AnyView { + AnyView(EmptyView()) + } init() {} } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Creation/CollapsibleProductCard.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Creation/CollapsibleProductCard.swift index e1f1f2237f3..fbe7bb26b48 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Creation/CollapsibleProductCard.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Creation/CollapsibleProductCard.swift @@ -1,5 +1,6 @@ import Yosemite import SwiftUI +import struct WooFoundation.ProductImageThumbnail /// Displays a single collapsible product row or grouped parent and child product rows struct CollapsibleProductCard: View { diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Creation/ProductsSection/ProductDiscountView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Creation/ProductsSection/ProductDiscountView.swift index f7bcac287c6..2566d829fe3 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Creation/ProductsSection/ProductDiscountView.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Creation/ProductsSection/ProductDiscountView.swift @@ -1,5 +1,6 @@ import SwiftUI import Yosemite +import struct WooFoundation.ProductImageThumbnail struct ProductDiscountView: View { private let viewModel: ProductDiscountViewModel diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Items Section/WooShippingItemRow.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Items Section/WooShippingItemRow.swift index 70bb5e9ec3d..98cf7c46d7e 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Items Section/WooShippingItemRow.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Items Section/WooShippingItemRow.swift @@ -1,4 +1,5 @@ import SwiftUI +import struct WooFoundation.ProductImageThumbnail /// Row for an item to ship with the Woo Shipping extension. struct WooShippingItemRow: View { diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/SelectableShipmentItemRow.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/SelectableShipmentItemRow.swift index 90ae2145919..3e89beef327 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/SelectableShipmentItemRow.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/SelectableShipmentItemRow.swift @@ -1,5 +1,6 @@ import Yosemite import SwiftUI +import struct WooFoundation.ProductImageThumbnail /// Row for a selectable shipment item to ship with the Woo Shipping extension. struct SelectableShipmentItemRow: View { diff --git a/WooCommerce/Classes/ViewRelated/Products/ProductSelector/ProductRow.swift b/WooCommerce/Classes/ViewRelated/Products/ProductSelector/ProductRow.swift index dcb3e56401f..b5cb4bbd59b 100644 --- a/WooCommerce/Classes/ViewRelated/Products/ProductSelector/ProductRow.swift +++ b/WooCommerce/Classes/ViewRelated/Products/ProductSelector/ProductRow.swift @@ -1,4 +1,5 @@ import SwiftUI +import struct WooFoundation.ProductImageThumbnail /// Represent a single product or variation row in the Product section of a New Order or in the ProductSelectorView /// diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 884a2df1335..35dcc9f2288 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -25,6 +25,9 @@ 0105865E2E426FDC002FADD1 /* UIKitBarcodeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105865D2E426FDB002FADD1 /* UIKitBarcodeObserver.swift */; }; 01058DD02E42716A002FADD1 /* BarcodeScannerContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01058DCF2E42716A002FADD1 /* BarcodeScannerContainerTests.swift */; }; 01058DD22E4273F2002FADD1 /* UIKitBarcodeObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01058DD12E4273F2002FADD1 /* UIKitBarcodeObserverTests.swift */; }; + 010F7D872E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010F7D862E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift */; }; + 010F7D8B2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010F7D8A2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift */; }; + 010F7D8D2E7A8447002B02EA /* ProductImageThumbnail+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010F7D8C2E7A8447002B02EA /* ProductImageThumbnail+Extensions.swift */; }; 011D396F2D09FCD200DB1445 /* CardPresentModalLocationRequired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011D396E2D09FCCB00DB1445 /* CardPresentModalLocationRequired.swift */; }; 011D39712D0A324200DB1445 /* LocationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011D39702D0A324100DB1445 /* LocationServiceTests.swift */; }; 011D7A332CEC877A0007C187 /* CardPresentModalNonRetryableErrorEmailSent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011D7A322CEC87770007C187 /* CardPresentModalNonRetryableErrorEmailSent.swift */; }; @@ -3236,6 +3239,9 @@ 0105865D2E426FDB002FADD1 /* UIKitBarcodeObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBarcodeObserver.swift; sourceTree = ""; }; 01058DCF2E42716A002FADD1 /* BarcodeScannerContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarcodeScannerContainerTests.swift; sourceTree = ""; }; 01058DD12E4273F2002FADD1 /* UIKitBarcodeObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBarcodeObserverTests.swift; sourceTree = ""; }; + 010F7D862E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSFormattableAmountTextFieldAdaptor.swift; sourceTree = ""; }; + 010F7D8A2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCouponCreationSheetAdaptor.swift; sourceTree = ""; }; + 010F7D8C2E7A8447002B02EA /* ProductImageThumbnail+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductImageThumbnail+Extensions.swift"; sourceTree = ""; }; 011D396E2D09FCCB00DB1445 /* CardPresentModalLocationRequired.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalLocationRequired.swift; sourceTree = ""; }; 011D39702D0A324100DB1445 /* LocationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServiceTests.swift; sourceTree = ""; }; 011D7A322CEC87770007C187 /* CardPresentModalNonRetryableErrorEmailSent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalNonRetryableErrorEmailSent.swift; sourceTree = ""; }; @@ -6480,6 +6486,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 010F7D852E79B39A002B02EA /* View Adaptors */ = { + isa = PBXGroup; + children = ( + 010F7D8A2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift */, + 010F7D862E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift */, + ); + path = "View Adaptors"; + sourceTree = ""; + }; 014BD4B62C64E26B0011A66E /* Order Messages */ = { isa = PBXGroup; children = ( @@ -6527,6 +6542,7 @@ 01654EB02E786223001DBB6F /* Adaptors */ = { isa = PBXGroup; children = ( + 010F7D852E79B39A002B02EA /* View Adaptors */, 016582D42E78715B001DBB6F /* Card Present Payments */, 016582D52E78715B001DBB6F /* POSCollectOrderPaymentAnalyticsAdaptor.swift */, 01654EAF2E786223001DBB6F /* POSServiceLocatorAdaptor.swift */, @@ -12138,6 +12154,7 @@ CE1CCB4C20572444000EE3AC /* Extensions */ = { isa = PBXGroup; children = ( + 010F7D8C2E7A8447002B02EA /* ProductImageThumbnail+Extensions.swift */, 016582EC2E7897B3001DBB6F /* String+Helpers.swift */, 2D88C1102DF883BD00A6FB2C /* AttributedString+Helpers.swift */, 2004E2DB2C08E95B00D62521 /* ViewControllerPresenting.swift */, @@ -13394,7 +13411,6 @@ CC53FB3427551A6E00C4CA4F /* ProductRow.swift */, CC53FB39275697B000C4CA4F /* ProductRowViewModel.swift */, B9B7E2E529FBF96100F9CED1 /* ProductSelectorViewModelTracker.swift */, - 68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */, 02DFD5032B20486C0048CD70 /* ProductStepper.swift */, 02DFD5052B2048C50048CD70 /* ProductStepperViewModel.swift */, 02FE734A2B21613D00CD486B /* ProductWithQuantityStepperView.swift */, @@ -16013,7 +16029,6 @@ 262A098B2628C51D0033AD20 /* OrderAddOnListViewModel.swift in Sources */, 0262DA5323A238460029AF30 /* UnitInputTableViewCell.swift in Sources */, D8EE9692264D328A0033B2F9 /* LegacyReceiptViewController.swift in Sources */, - 68AC9D292ACE598B0042F784 /* ProductImageThumbnail.swift in Sources */, CE4FE7D82B7D306200F66DD5 /* MultiSelectionReorderableList.swift in Sources */, CE6E110D2C91E5FF00563DD4 /* WooShippingItems.swift in Sources */, 029106BE2BE2868F00C2248B /* CollapsibleOrderFormCardBorder.swift in Sources */, @@ -16371,6 +16386,7 @@ 95B6C6102D9DADAA00E1A661 /* WPComLoginGravatarView.swift in Sources */, 028FF8E32AA1E1C60038964F /* ProductDetailsCellViewModel+AddOns.swift in Sources */, DEC2962726C17AD8005A056B /* ShippingLabelCustomsForm+Localization.swift in Sources */, + 010F7D8D2E7A8447002B02EA /* ProductImageThumbnail+Extensions.swift in Sources */, 26A630FE253F63C300CBC3B1 /* RefundableOrderItem.swift in Sources */, CEE006052077D1280079161F /* SummaryTableViewCell.swift in Sources */, DEE215342D1297CD004A11F3 /* UserDefaults+EditStoreList.swift in Sources */, @@ -16466,6 +16482,7 @@ DEA66A1B2E41E0C000791018 /* BlazeCampaignProduct.swift in Sources */, CE6E110B2C91DA5D00563DD4 /* WooShippingItemRow.swift in Sources */, B946880E29B627EB000646B0 /* SearchableActivityConvertable.swift in Sources */, + 010F7D8B2E79B763002B02EA /* POSCouponCreationSheetAdaptor.swift in Sources */, 027ADB732D21812D009608DB /* POSItemImageView.swift in Sources */, EE09DE0B2C2D6E5100A32680 /* SelectPackageImageCoordinator.swift in Sources */, DE621F6A29D67E1B000DE3BD /* WooAnalyticsEvent+JetpackSetup.swift in Sources */, @@ -16898,6 +16915,7 @@ DA41043A2C247B6900E8456A /* PointOfSalePreviewOrderController.swift in Sources */, 20F6A46C2DE5FCEF0066D8CB /* POSItemFetchAnalytics.swift in Sources */, B933CCB02AA6220E00938F3F /* TaxRateRow.swift in Sources */, + 010F7D872E79B39E002B02EA /* POSFormattableAmountTextFieldAdaptor.swift in Sources */, 57532CAC24BFF4DA0032B84E /* MessageComposerPresenter.swift in Sources */, DE4D23AE29B1B0EF003A4B5D /* WPCom2FALoginViewModel.swift in Sources */, 0270F47824D006F60005210A /* ProductFormPresentationStyle.swift in Sources */,