Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Modules/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ let package = Package(
),
.target(
name: "WooFoundation",
dependencies: ["WooFoundationCore"]
dependencies: [
"WooFoundationCore",
.product(name: "Kingfisher", package: "Kingfisher")
]
),
.target(
name: "WooFoundationCore",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import SwiftUI
import Kingfisher

struct ProductImageThumbnail<Placeholder: View>: View {
public struct ProductImageThumbnail<Placeholder: View>: View {
private let productImageURL: URL?
private let productImageSize: CGFloat
private let scale: CGFloat
Expand All @@ -25,13 +25,13 @@ struct ProductImageThumbnail<Placeholder: View>: 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
Expand All @@ -41,7 +41,7 @@ struct ProductImageThumbnail<Placeholder: View>: View {
self.placeholder = placeholder()
}

var body: some View {
public var body: some View {
KFImage
.url(productImageURL)
.cacheOriginalImage(cachesOriginalImage)
Expand All @@ -57,22 +57,3 @@ struct ProductImageThumbnail<Placeholder: View>: 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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
33 changes: 32 additions & 1 deletion WooCommerce/Classes/POS/Adaptors/POSServiceLocatorAdaptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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<Bool>,
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<Bool>,
title: String,
cancelButtonTitle: String,
onSelection: @escaping (Coupon.DiscountType) -> Void) -> AnyView {
AnyView(POSDiscountTypeSelectionSheetAdaptor(
isPresented: isPresented,
title: title,
cancelButtonTitle: cancelButtonTitle,
onSelection: onSelection
))
}
}
Original file line number Diff line number Diff line change
@@ -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 var showTypeSelection: Bool
let dismissHandler: () -> Void
let onDisappear: () -> Void

init(discountType: Coupon.DiscountType,
showTypeSelection: Binding<Bool>,
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()
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ 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?

func body(content: Content) -> some View {
content
.posSheet(item: $selectedType) { (posDiscountType: POSCouponDiscountType) in
POSCouponCreationView(
externalViews.createCouponCreationView(
discountType: posDiscountType.discountType,
showTypeSelection: $showCouponSelectionSheet,
onSuccess: { coupon in
Expand All @@ -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<Bool>,
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<Bool>,
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)
}
}
}
}

Expand Down
Loading