Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 private var showTypeSelection: Bool
private let dismissHandler: () -> Void
private 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