Skip to content

Commit

Permalink
[Woo POS] [Variable Products] Add parent product to the POSItem enum,…
Browse files Browse the repository at this point in the history
… and make UI (#14771)
  • Loading branch information
jaclync authored Dec 31, 2024
2 parents 7c42ba8 + 093b952 commit 0fbafb4
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 33 deletions.
6 changes: 6 additions & 0 deletions WooCommerce/Classes/POS/Presentation/ItemListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ private extension ItemListView {
} label: {
SimpleProductCardView(product: simpleProduct)
}
case .parentProduct(let parentProduct):
Button {
print("tapped parent product")
} label: {
ParentProductCardView(parentProduct: parentProduct)
}
}
}
}
Expand Down
82 changes: 82 additions & 0 deletions WooCommerce/Classes/POS/Presentation/ParentProductCardView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import SwiftUI
import struct Yosemite.POSParentProduct

/// Displays a card for a parent product in POS.
struct ParentProductCardView: View {
private let parentProduct: POSParentProduct

@ScaledMetric private var scale: CGFloat = 1.0
@Environment(\.dynamicTypeSize) var dynamicTypeSize

init(parentProduct: POSParentProduct) {
self.parentProduct = parentProduct
}

var body: some View {
HStack(spacing: Constants.cardSpacing) {
POSItemImageView(imageSource: parentProduct.productImageSource,
imageSize: Constants.productCardSize * scale,
scale: scale)
.frame(width: min(Constants.productCardSize * scale, Constants.maximumProductCardSize),
height: Constants.productCardSize * scale)
.clipped()

VStack(alignment: .leading, spacing: Constants.textSpacing) {
Text(parentProduct.name)
.lineLimit(2)
.foregroundStyle(Color.posPrimaryText)
.multilineTextAlignment(.leading)
.font(Constants.itemNameFont)

detailView()
}
.padding(.horizontal, Constants.horizontalTextPadding * (1 / scale))
.padding(.vertical, Constants.verticalTextPadding * (1 / scale))
Spacer()
}
.frame(maxWidth: .infinity, idealHeight: Constants.productCardSize * scale)
.background(Color.posSecondaryBackground)
.posItemCardBorderStyles()
}
}

private extension ParentProductCardView {
@ViewBuilder
func detailView() -> some View {
switch parentProduct.type {
case .variable:
Text(Localization.variationsAvailable)
.foregroundStyle(Color.posSecondaryText)
.font(.posBodyRegular)
}
}
}

private extension ParentProductCardView {
enum Localization {
static let variationsAvailable = NSLocalizedString(
"pos.parentProductCard.optionsAvailable",
value: "Options available",
comment: "Text indicating that there are options available for a parent product"
)
}
}

private extension ParentProductCardView {
enum Constants {
static let productCardSize: CGFloat = 112
static let maximumProductCardSize: CGFloat = Constants.productCardSize * 2
static let cardSpacing: CGFloat = 0
static let textSpacing: CGFloat = 8
static let horizontalTextPadding: CGFloat = 32
static let verticalTextPadding: CGFloat = 8
static let itemNameFont: POSFontStyle = .posBodyEmphasized
}
}

#if DEBUG
#Preview {
let parentProduct = POSParentProduct(id: UUID(), name: "Parent variable product", productImageSource: nil, productID: 42, type: .variable)
ParentProductCardView(parentProduct: parentProduct)
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import SwiftUI

struct POSItemCardBorderStylesModifier: ViewModifier {
func body(content: Content) -> some View {
content
.overlay {
RoundedRectangle(cornerRadius: Constants.cardCornerRadius)
.stroke(Color.black, lineWidth: Constants.nilOutline)
}
.clipShape(RoundedRectangle(cornerRadius: Constants.cardCornerRadius))
.shadow(color: Color.black.opacity(0.08), radius: 4, y: 2)
}
}

private extension POSItemCardBorderStylesModifier {
enum Constants {
static let cardCornerRadius: CGFloat = 8.0
// The use of stroke means the shape is rendered as an outline (border) rather than a filled shape,
// since we still have to give it a value, we use 0 so it renders no border but it's shaped as one.
static let nilOutline: CGFloat = 0
}
}

extension View {
/// Applies the POS item card border styles to the view.
func posItemCardBorderStyles() -> some View {
self.modifier(POSItemCardBorderStylesModifier())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import SwiftUI

/// A view that displays an image in a POS item view.
struct POSItemImageView: View {
let imageSource: String?
let imageSize: CGFloat
let scale: CGFloat

var body: some View {
if let imageSource = imageSource {
ProductImageThumbnail(productImageURL: URL(string: imageSource),
productImageSize: imageSize,
scale: scale,
foregroundColor: .clear,
cachesOriginalImage: true)
} else {
Rectangle()
.foregroundColor(Color(.secondarySystemFill))
}
}
}

#Preview("Placeholder") {
POSItemImageView(imageSource: nil,
imageSize: 112,
scale: 1)
}

#Preview("Image") {
POSItemImageView(imageSource: "https://pd.w.org/2024/12/986762d0d4d4cf17.82435881-scaled.jpeg",
imageSize: 112,
scale: 1)
}
32 changes: 7 additions & 25 deletions WooCommerce/Classes/POS/Presentation/SimpleProductCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,12 @@ struct SimpleProductCardView: View {

var body: some View {
HStack(spacing: Constants.cardSpacing) {
if let imageSource = product.productImageSource {
ProductImageThumbnail(productImageURL: URL(string: imageSource),
productImageSize: Constants.productCardSize * scale,
scale: scale,
foregroundColor: .clear,
cachesOriginalImage: true)
.frame(width: min(Constants.productCardSize * scale, Constants.maximumProductCardSize),
height: Constants.productCardSize * scale)
.clipped()
} else {
Rectangle()
.frame(width: min(Constants.productCardSize * scale, Constants.maximumProductCardSize),
height: Constants.productCardSize * scale)
.foregroundColor(Color(.secondarySystemFill))
}
POSItemImageView(imageSource: product.productImageSource,
imageSize: Constants.productCardSize * scale,
scale: scale)
.frame(width: min(Constants.productCardSize * scale, Constants.maximumProductCardSize),
height: Constants.productCardSize * scale)
.clipped()

DynamicHStack(spacing: Constants.textSpacing) {
Spacer().renderedIf(dynamicTypeSize.isAccessibilitySize)
Expand All @@ -48,23 +39,14 @@ struct SimpleProductCardView: View {
}
.frame(maxWidth: .infinity, idealHeight: Constants.productCardSize * scale)
.background(Color.posSecondaryBackground)
.overlay {
RoundedRectangle(cornerRadius: Constants.productCardCornerRadius)
.stroke(Color.black, lineWidth: Constants.nilOutline)
}
.clipShape(RoundedRectangle(cornerRadius: Constants.productCardCornerRadius))
.shadow(color: Color.black.opacity(0.08), radius: 4, y: 2)
.posItemCardBorderStyles()
}
}

private extension SimpleProductCardView {
enum Constants {
static let productCardSize: CGFloat = 112
static let maximumProductCardSize: CGFloat = Constants.productCardSize * 2
static let productCardCornerRadius: CGFloat = 8
// The use of stroke means the shape is rendered as an outline (border) rather than a filled shape,
// since we still have to give it a value, we use 0 so it renders no border but it's shaped as one.
static let nilOutline: CGFloat = 0
static let cardSpacing: CGFloat = 0
static let textSpacing: CGFloat = 8
static let horizontalTextPadding: CGFloat = 32
Expand Down
22 changes: 21 additions & 1 deletion WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 70;
objects = {

/* Begin PBXAggregateTarget section */
Expand Down Expand Up @@ -374,6 +374,9 @@
0279F0E4252DC9670098D7DE /* ProductVariationLoadUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0279F0E3252DC9670098D7DE /* ProductVariationLoadUseCase.swift */; };
027A2E142513124E00DA6ACB /* Keychain+Entries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027A2E132513124E00DA6ACB /* Keychain+Entries.swift */; };
027A2E162513356100DA6ACB /* AppleIDCredentialChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027A2E152513356100DA6ACB /* AppleIDCredentialChecker.swift */; };
027ADB6E2D1BF5E3009608DB /* ParentProductCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027ADB6D2D1BF5E3009608DB /* ParentProductCardView.swift */; };
027ADB732D21812D009608DB /* POSItemImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027ADB722D21812D009608DB /* POSItemImageView.swift */; };
027ADB752D218A8D009608DB /* POSItemCardBorderStylesModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027ADB742D218A8D009608DB /* POSItemCardBorderStylesModifier.swift */; };
027B8BB823FE0CB30040944E /* DefaultProductUIImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027B8BB723FE0CB30040944E /* DefaultProductUIImageLoader.swift */; };
027B8BBD23FE0DE10040944E /* ProductImageActionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027B8BBC23FE0DE10040944E /* ProductImageActionHandlerTests.swift */; };
027B8BBF23FE0F850040944E /* MockMediaStoresManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027B8BBE23FE0F850040944E /* MockMediaStoresManager.swift */; };
Expand Down Expand Up @@ -3526,6 +3529,9 @@
0279F0E3252DC9670098D7DE /* ProductVariationLoadUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationLoadUseCase.swift; sourceTree = "<group>"; };
027A2E132513124E00DA6ACB /* Keychain+Entries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Keychain+Entries.swift"; sourceTree = "<group>"; };
027A2E152513356100DA6ACB /* AppleIDCredentialChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIDCredentialChecker.swift; sourceTree = "<group>"; };
027ADB6D2D1BF5E3009608DB /* ParentProductCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParentProductCardView.swift; sourceTree = "<group>"; };
027ADB722D21812D009608DB /* POSItemImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSItemImageView.swift; sourceTree = "<group>"; };
027ADB742D218A8D009608DB /* POSItemCardBorderStylesModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSItemCardBorderStylesModifier.swift; sourceTree = "<group>"; };
027B8BB723FE0CB30040944E /* DefaultProductUIImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultProductUIImageLoader.swift; sourceTree = "<group>"; };
027B8BBC23FE0DE10040944E /* ProductImageActionHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductImageActionHandlerTests.swift; sourceTree = "<group>"; };
027B8BBE23FE0F850040944E /* MockMediaStoresManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaStoresManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6159,6 +6165,10 @@
FEED57F92686544D00E47FD9 /* RoleErrorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoleErrorViewModelTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFileSystemSynchronizedRootGroup section */
027ADB6F2D2180FB009608DB /* Product Card Components */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "Product Card Components"; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */

/* Begin PBXFrameworksBuildPhase section */
260837042AA66E4A0004A12B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
Expand Down Expand Up @@ -6312,6 +6322,8 @@
68D3E98C2C7C371B005B6278 /* POSBottomShadowViewModifier.swift */,
683AC4AB2CEF019700FF0A5E /* POSSendReceiptView.swift */,
20D2CCA22C7E175700051705 /* WavesProgressViewStyle.swift */,
027ADB722D21812D009608DB /* POSItemImageView.swift */,
027ADB742D218A8D009608DB /* POSItemCardBorderStylesModifier.swift */,
);
path = "Reusable Views";
sourceTree = "<group>";
Expand Down Expand Up @@ -6971,6 +6983,7 @@
026826A12BF59DED0036F959 /* Presentation */ = {
isa = PBXGroup;
children = (
027ADB6F2D2180FB009608DB /* Product Card Components */,
01620C4C2C5394A400D3EA2F /* Reusable Views */,
02E71DB42C118C470036C2FD /* Card Present Payments */,
026826B02BF59E170036F959 /* CardReaderConnection */,
Expand All @@ -6989,6 +7002,7 @@
68D8FBD02BFEF9C700477C42 /* TotalsView.swift */,
68AF3C3A2D01481A006F1ED2 /* POSReceiptEligibilityBanner.swift */,
DA1D68C12C36F0980097859A /* PointOfSaleAssets.swift */,
027ADB6D2D1BF5E3009608DB /* ParentProductCardView.swift */,
);
path = Presentation;
sourceTree = "<group>";
Expand Down Expand Up @@ -14023,6 +14037,9 @@
260837142AA66E4B0004A12B /* PBXTargetDependency */,
26F81B212BE433A3009EC58E /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
027ADB6F2D2180FB009608DB /* Product Card Components */,
);
name = WooCommerce;
packageProductDependencies = (
263E37E02641AD8300260D3B /* Codegen */,
Expand Down Expand Up @@ -14988,6 +15005,7 @@
B509FED121C041DF000076A9 /* Locale+Woo.swift in Sources */,
B5DB01B52114AB2D00A4F797 /* WooCrashLoggingStack.swift in Sources */,
205B7EBF2C19FBCA00D14A36 /* PointOfSaleCardPresentPaymentRequiredReaderUpdateInProgressAlertViewModel.swift in Sources */,
027ADB6E2D1BF5E3009608DB /* ParentProductCardView.swift in Sources */,
26ED9660274328BC00FA00A1 /* SimplePaymentsSummaryViewModel.swift in Sources */,
024DF31E23743045006658FE /* TextList+AztecFormatting.swift in Sources */,
CEEF742E2B9A0BAA00B03948 /* SessionsReportCardViewModel.swift in Sources */,
Expand Down Expand Up @@ -15449,6 +15467,7 @@
02490D1A284DE664002096EF /* ProductImagesSaver.swift in Sources */,
8646A9BA2B46C7CA001F606C /* BlazeAdDestinationSettingView.swift in Sources */,
205E794F2C207D38001BA266 /* PointOfSaleCardPresentPaymentNonRetryableErrorMessageView.swift in Sources */,
027ADB752D218A8D009608DB /* POSItemCardBorderStylesModifier.swift in Sources */,
0215320B24231D5A003F2BBD /* UIStackView+Subviews.swift in Sources */,
02F4F50B237AEB8A00E13A9C /* ProductFormTableViewDataSource.swift in Sources */,
2004E2CC2C07795E00D62521 /* CardPresentPaymentError.swift in Sources */,
Expand Down Expand Up @@ -16133,6 +16152,7 @@
DEF8CF0F29A890E900800A60 /* JetpackBenefitsViewModel.swift in Sources */,
CE6E110B2C91DA5D00563DD4 /* WooShippingItemRow.swift in Sources */,
B946880E29B627EB000646B0 /* SearchableActivityConvertable.swift in Sources */,
027ADB732D21812D009608DB /* POSItemImageView.swift in Sources */,
EE09DE0B2C2D6E5100A32680 /* SelectPackageImageCoordinator.swift in Sources */,
DE621F6A29D67E1B000DE3BD /* WooAnalyticsEvent+JetpackSetup.swift in Sources */,
DE78DE422B2813E4002E58DE /* ThemesCarouselViewModel.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions Yosemite/Yosemite.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
026CF62C237D92DC009563D4 /* ProductVariationAttribute+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026CF62B237D92DC009563D4 /* ProductVariationAttribute+ReadOnlyConvertible.swift */; };
026D52C0238235930092AE05 /* ProductVariationStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026D52BF238235930092AE05 /* ProductVariationStoreTests.swift */; };
0271E1662509CF0100633F7A /* AnyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0271E1652509CF0100633F7A /* AnyError.swift */; };
027ADB6C2D1BF3AD009608DB /* POSParentProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027ADB6B2D1BF3AD009608DB /* POSParentProduct.swift */; };
027CC11129F7AAEA00614B6E /* MockGenerativeContentRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027CC11029F7AAEA00614B6E /* MockGenerativeContentRemote.swift */; };
0286A1B82A0CBDC40099EF94 /* FeatureFlagStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0286A1B72A0CBDC40099EF94 /* FeatureFlagStore.swift */; };
0286A1BA2A0CBE1B0099EF94 /* FeatureFlagAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0286A1B92A0CBE1B0099EF94 /* FeatureFlagAction.swift */; };
Expand Down Expand Up @@ -596,6 +597,7 @@
026CF62B237D92DC009563D4 /* ProductVariationAttribute+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductVariationAttribute+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
026D52BF238235930092AE05 /* ProductVariationStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductVariationStoreTests.swift; sourceTree = "<group>"; };
0271E1652509CF0100633F7A /* AnyError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyError.swift; sourceTree = "<group>"; };
027ADB6B2D1BF3AD009608DB /* POSParentProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSParentProduct.swift; sourceTree = "<group>"; };
027CC11029F7AAEA00614B6E /* MockGenerativeContentRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGenerativeContentRemote.swift; sourceTree = "<group>"; };
0286A1B72A0CBDC40099EF94 /* FeatureFlagStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagStore.swift; sourceTree = "<group>"; };
0286A1B92A0CBE1B0099EF94 /* FeatureFlagAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagAction.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1516,6 +1518,7 @@
children = (
6898F3732C0842150039F10A /* PointOfSaleItemServiceProtocol.swift */,
68EA25332C08734800C49AE2 /* POSSimpleProduct.swift */,
027ADB6B2D1BF3AD009608DB /* POSParentProduct.swift */,
68EA25372C0876DF00C49AE2 /* PointOfSaleProductService.swift */,
);
path = PointOfSale;
Expand Down Expand Up @@ -2599,6 +2602,7 @@
450106872399AB3F00E24722 /* TaxClass+ReadOnlyConvertible.swift in Sources */,
B9AECD462851DBED00E78584 /* Order+CurrencyFormattedValues.swift in Sources */,
45182D1F27B54D3000B4C05C /* InboxNote+ReadOnlyConvertible.swift in Sources */,
027ADB6C2D1BF3AD009608DB /* POSParentProduct.swift in Sources */,
022F00C224726090008CD97F /* SiteNotificationCountFileContents.swift in Sources */,
247CE7BC2582DC1E00F9D9D1 /* MockCustomer.swift in Sources */,
CE0FBB1D2D0C5DE3008B7789 /* WooShippingCarrierPredefinedOptions+ReadOnlyConvertible.swift in Sources */,
Expand Down
21 changes: 21 additions & 0 deletions Yosemite/Yosemite/PointOfSale/POSParentProduct.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

public enum POSParentProductType {
case variable
}

public struct POSParentProduct: Equatable, Hashable, Identifiable {
public let id: UUID
public let name: String
public let productImageSource: String?
public let productID: Int64
public let type: POSParentProductType

public init(id: UUID, name: String, productImageSource: String?, productID: Int64, type: POSParentProductType) {
self.id = id
self.name = name
self.productImageSource = productImageSource
self.productID = productID
self.type = type
}
}
Loading

0 comments on commit 0fbafb4

Please sign in to comment.