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
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public struct WooShippingLabelData: Decodable, Equatable {
/// Labels purchased for the current order
public let currentOrderLabels: [ShippingLabelPurchase]

init(currentOrderLabels: [ShippingLabelPurchase]) {
public init(currentOrderLabels: [ShippingLabelPurchase]) {
self.currentOrderLabels = currentOrderLabels
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ extension ShippingLabelPurchase: Decodable {
let productIDs = try container.decodeIfPresent([Int64].self, forKey: .productIDs) ?? []
let productNames = try container.decode([String].self, forKey: .productNames)

let shipmentID = try container.decodeIfPresent(String.self, forKey: .shipmentID)
let shipmentID = container.failsafeDecodeIfPresent(targetType: String.self,
forKey: .shipmentID,
alternativeTypes: [.integer(transform: { String($0) })])
Comment on lines +107 to +109
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While testing again I saw that the shipment ID was somehow returned as a number today. This is a workaround to transform the number to our expected string type.


self.init(siteID: siteID,
orderID: orderID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@ final class OrderDetailsDataSource: NSObject {
/// Whether the button to create shipping labels should be visible.
///
var shouldShowShippingLabelCreation: Bool {
return isEligibleForShippingLabelCreation && shippingLabels.nonRefunded.isEmpty &&
!isEligibleForPayment
if featureFlags.isFeatureFlagEnabled(.revampedShippingLabelCreation) {
// TODO-15375: update logic to show shipping label creation button
return isEligibleForShippingLabelCreation && !isEligibleForPayment
}
return isEligibleForShippingLabelCreation && shippingLabels.nonRefunded.isEmpty && !isEligibleForPayment
}

/// Whether the option to re-create shipping labels should be visible.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ final class CollapsibleShipmentItemCardViewModel: ObservableObject, Identifiable
}

init(item: ShippingLabelPackageItem,
isSelectable: Bool = true,
currency: String) {
self.packageItem = item

Expand All @@ -34,7 +35,7 @@ final class CollapsibleShipmentItemCardViewModel: ObservableObject, Identifiable
currency: currency)

self.mainItemRow = SelectableShipmentItemRowViewModel(itemID: "\(item.orderItemID)",
isSelectable: true,
isSelectable: isSelectable,
item: mainShippingItem,
showQuantity: true)

Expand All @@ -45,7 +46,7 @@ final class CollapsibleShipmentItemCardViewModel: ObservableObject, Identifiable
for index in 0..<item.quantity.intValue {
let childShipmentId = "\(item.orderItemID)-sub-\(index)"
childItemRows.append(SelectableShipmentItemRowViewModel(itemID: childShipmentId,
isSelectable: true,
isSelectable: isSelectable,
item: childShippingItem,
showQuantity: false))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ struct WooShippingSplitShipmentsDetailView: View {

ScrollView {
VStack(alignment: .leading, spacing: Layout.contentPadding) {
if viewModel.currentShipment.isPurchased {
fulfilledShipmentView
}

AdaptiveStack(horizontalAlignment: .leading) {
Text(viewModel.itemsCountLabel)
.headlineStyle()
Expand Down Expand Up @@ -102,7 +106,9 @@ private extension WooShippingSplitShipmentsDetailView {
selectedTabIndicatorHeight: Layout.selectedTabIndicatorHeight,
tabPadding: Layout.tabPadding,
tabsNameFont: Font.subheadline.bold(),
tabsIconSize: nil,
tabsIconSize: Layout.purchasedIconWidth,
tabsIconAlignment: .trailing,
tabsIconForegroundColor: Layout.green,
tabItemContentHorizontalPadding: Layout.tabItemContentHorizontalPadding,
tabItemContentVerticalPadding: Layout.tabItemContentVerticalPadding)
.overlay(alignment: .trailing) {
Expand All @@ -121,7 +127,7 @@ private extension WooShippingSplitShipmentsDetailView {
Button(String.localizedStringWithFormat(Localization.removeShipmentFormat,
viewModel.retrieveName(for: shipment).lowercased())) {
shipmentToRemove = shipment
}
}.disabled(shipment.isPurchased)
}
Divider()
Button(Localization.mergeAll) {
Expand All @@ -133,6 +139,23 @@ private extension WooShippingSplitShipmentsDetailView {
}
}

var fulfilledShipmentView: some View {
VStack {
Text(Localization.PurchasedShipment.title)
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
Text(Localization.PurchasedShipment.subtitle)
.frame(maxWidth: .infinity, alignment: .leading)
}
.multilineTextAlignment(.leading)
.foregroundStyle(Layout.green)
.padding(Layout.contentPadding)
.background(
Layout.greenBackground
.clipShape(RoundedRectangle(cornerRadius: Layout.cornerRadius))
)
}

var noticeStack: some View {
VStack(spacing: Layout.contentPadding) {
if let message = viewModel.instructions {
Expand Down Expand Up @@ -208,36 +231,37 @@ private extension WooShippingSplitShipmentsDetailView {

VStack {
ForEach(viewModel.shipmentsToMerge(for: shipment)) { otherShipment in
HStack {
Image(systemName: "arrow.turn.down.right")
.foregroundStyle(otherShipment == shipmentToMergeInto ? Color.accentColor : Color.secondary)
.subheadlineStyle()
.bold()
VStack(alignment: .leading) {
Text(viewModel.retrieveName(for: otherShipment))
.headlineStyle()
AdaptiveStack(horizontalAlignment: .leading) {
Text(otherShipment.quantity)
Spacer()
Text(otherShipment.itemsDetailLabel)
Button {
shipmentToMergeInto = otherShipment
} label: {
HStack {
Image(systemName: "arrow.turn.down.right")
.foregroundStyle(otherShipment == shipmentToMergeInto ? Color.accentColor : Color.secondary)
.font(.subheadline)
.bold()
VStack(alignment: .leading) {
Text(viewModel.retrieveName(for: otherShipment))
.font(.headline)
AdaptiveStack(horizontalAlignment: .leading) {
Text(otherShipment.quantity)
Spacer()
Text(otherShipment.itemsDetailLabel)
}
.font(.subheadline)
}
.font(.subheadline)
}
.padding(Layout.contentPadding)
.if(otherShipment == shipmentToMergeInto) { view in
view.background(
Color(.listSelectedBackground)
.clipShape(RoundedRectangle(cornerRadius: Layout.cornerRadius))
)
}
.roundedBorder(cornerRadius: Layout.cornerRadius,
lineColor: otherShipment == shipmentToMergeInto ? .accentColor : Color(.separator),
lineWidth: otherShipment == shipmentToMergeInto ? 2 : 1)
}
.padding(Layout.contentPadding)
.if(otherShipment == shipmentToMergeInto) { view in
view.background(
Color(.listSelectedBackground)
.clipShape(RoundedRectangle(cornerRadius: Layout.cornerRadius))
)
}
.roundedBorder(cornerRadius: Layout.cornerRadius,
lineColor: otherShipment == shipmentToMergeInto ? .accentColor : Color(.separator),
lineWidth: otherShipment == shipmentToMergeInto ? 2 : 1)
.contentShape(Rectangle())
.onTapGesture {
shipmentToMergeInto = otherShipment
}
.disabled(otherShipment.isPurchased)
}
}

Expand Down Expand Up @@ -325,6 +349,11 @@ fileprivate extension WooShippingSplitShipmentsDetailView {
static let tabItemContentVerticalPadding: CGFloat = 9.0
static let cornerRadius: CGFloat = 8
static let gradientViewWidth: CGFloat = 32
static let purchasedIconWidth: CGFloat = 16

static let green = Color(UIColor(light: .withColorStudio(.green, shade: .shade60),
dark: .withColorStudio(.green, shade: .shade40)))
static let greenBackground = Color.withColorStudio(name: .green, shade: .shade0)
}

enum Localization {
Expand Down Expand Up @@ -404,6 +433,19 @@ fileprivate extension WooShippingSplitShipmentsDetailView {
"Reads as: 'Remove Shipment 1.'"
)
}

enum PurchasedShipment {
static let title = NSLocalizedString(
"wooShippingSplitShipmentsDetailView.purchasedShipment.title",
value: "You purchased a label for this shipment.",
comment: "Title label displayed on a shipment whose label is purchased in the shipping label creation flow."
)
static let subtitle = NSLocalizedString(
"wooShippingSplitShipmentsDetailView.purchasedShipment.subtitle",
value: "You can't move products into or out of it.",
comment: "Subtitle label displayed on a shipment whose label is purchased in the shipping label creation flow."
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,13 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject {
"\(itemsWeightLabel) • \(itemsPriceLabel)"
}

private let purchasedIcon = UIImage(systemName: "checkmark.circle.fill")?.withRenderingMode(.alwaysTemplate)

var topTabItems: [TopTabItem<EmptyView>] {
shipments.enumerated().map { (index, item) in
TopTabItem(name: String.localizedStringWithFormat(Localization.shipmentFormat, index + 1),
content: { EmptyView() })
return TopTabItem(name: String.localizedStringWithFormat(Localization.shipmentFormat, index + 1),
icon: item.isPurchased ? purchasedIcon : nil,
content: { EmptyView() })
}
}

Expand Down Expand Up @@ -109,14 +112,12 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject {
self.currencySettings = currencySettings
self.shippingSettingsService = shippingSettingsService

let contents = items.map { item in
CollapsibleShipmentItemCardViewModel(item: item, currency: order.currency)
}
let shipment = Shipment(contents: contents,
currency: order.currency,
currencySettings: currencySettings,
shippingSettingsService: shippingSettingsService)
self.shipments = [shipment]
self.shipments = Self.createShipments(with: config,
packageItems: items,
currency: order.currency,
currencySettings: currencySettings,
shippingSettingsService: shippingSettingsService)

shipmentsSavedInRemote = editedShipmentsInfo

configureSectionHeader()
Expand Down Expand Up @@ -266,10 +267,10 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject {
}

func mergeAllUnfulfilledShipments() {
let (unfulfilledShipments, fulfilledShipments) = shipments.partitioned(by: { $0.isPurchased })
var mergedShipmentContents = ShipmentContents()

// TODO-15440: check for fulfilled shipments and remove them from the list.
shipments.forEach { shipment in
unfulfilledShipments.forEach { shipment in
for item in shipment.contents {
let matchingItemIndex = mergedShipmentContents.firstIndex(where: {
$0.packageItem.productOrVariationID == item.packageItem.productOrVariationID
Expand All @@ -286,7 +287,7 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject {
}
}

shipments = [createShipment(with: mergedShipmentContents)]
shipments = [createShipment(with: mergedShipmentContents)] + fulfilledShipments
selectedShipmentIndex = 0
}

Expand Down Expand Up @@ -400,10 +401,62 @@ private extension WooShippingSplitShipmentsViewModel {
}
}

// MARK: Shipment
// MARK: Shipments

extension WooShippingSplitShipmentsViewModel {

func createShipment(with contents: [CollapsibleShipmentItemCardViewModel]) -> Shipment {
private static func createShipments(with config: WooShippingConfig,
packageItems: [ShippingLabelPackageItem],
currency: String,
currencySettings: CurrencySettings,
shippingSettingsService: ShippingSettingsService) -> [Shipment] {
guard config.shipments.isEmpty == false else {
let contents = packageItems.map { item in
CollapsibleShipmentItemCardViewModel(item: item, currency: currency)
}
let shipment = Shipment(contents: contents,
currency: currency,
currencySettings: currencySettings,
shippingSettingsService: shippingSettingsService)
return [shipment]
}

let currentOrderLabels = config.shippingLabelData?.currentOrderLabels ?? []
var shipments = [Shipment]()

for key in config.shipments.keys.sorted() {
guard let shipmentItems = config.shipments[key] else {
continue
}

let isPurchased = (currentOrderLabels.filter { $0.shipmentID == key}).isNotEmpty

var shipmentContents = ShipmentContents()
for shipmentItem in shipmentItems {
guard let packageItem = packageItems.first(where: { $0.orderItemID == shipmentItem.id }),
let subItems = shipmentItem.subItems else {
continue
}

let quantity = subItems.count > 0 ? subItems.count : 1
let updatedItem = ShippingLabelPackageItem(copy: packageItem, quantity: Decimal(quantity))
let content = CollapsibleShipmentItemCardViewModel(item: updatedItem,
isSelectable: !isPurchased,
currency: currency)
shipmentContents.append(content)
}

let shipment = Shipment(contents: shipmentContents,
isPurchased: isPurchased,
currency: currency,
currencySettings: currencySettings,
shippingSettingsService: shippingSettingsService)
shipments.append(shipment)
}
return shipments
}

private func createShipment(with contents: [CollapsibleShipmentItemCardViewModel]) -> Shipment {
Shipment(contents: contents,
currency: order.currency,
currencySettings: currencySettings,
Expand All @@ -414,6 +467,7 @@ extension WooShippingSplitShipmentsViewModel {

let id = UUID().uuidString
let contents: [CollapsibleShipmentItemCardViewModel]
let isPurchased: Bool

let quantity: String
let weight: String
Expand All @@ -426,10 +480,12 @@ extension WooShippingSplitShipmentsViewModel {
}

init(contents: [CollapsibleShipmentItemCardViewModel],
isPurchased: Bool = false,
currency: String,
currencySettings: CurrencySettings,
shippingSettingsService: ShippingSettingsService) {
self.contents = contents
self.isPurchased = isPurchased

let items = contents.map(\.packageItem)
let itemsCount = items.map(\.quantity).reduce(0, +)
Expand Down Expand Up @@ -465,7 +521,7 @@ extension WooShippingSplitShipmentsViewModel {

@discardableResult
@MainActor
func updateShipment() async throws -> WooShippingShipments {
private func updateShipment() async throws -> WooShippingShipments {
let shipments = editedShipmentsInfo
return try await withCheckedThrowingContinuation { continuation in
let action = WooShippingAction.updateShipment(siteID: order.siteID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,12 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
}
}

group.addTask {
await self.loadShipmentsInfo()
let totalOrderItems = order.items.map(\.quantity).reduce(0, +)
if totalOrderItems > 1 {
// Only fetch shipments info if there are more than one order items.
group.addTask {
await self.loadShipmentsInfo()
}
Comment on lines +278 to +283
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: We could move this totalOrderItems check logic into loadShipmentsInfo to keep the shipments-related logic inside one method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan to remove this check when working on #15309, so I'll keep this for now.

}
}

Expand Down Expand Up @@ -443,7 +447,6 @@ private extension WooShippingCreateLabelsViewModel {
stores.dispatch(action)
}

// TODO: Create view model only if order has more than 1 items that can be split into multiple shipments. (Check web logic)
if let config {
splitShipmentsViewModel = WooShippingSplitShipmentsViewModel(order: order,
config: config,
Expand Down
Loading