Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Shipping labels] Get account settings and use in UI custom packages #14499

Merged
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ba8743a
Load store options for custom package creation
bozidarsevo Nov 22, 2024
a18d6f1
Update WooShippingAddCustomPackageViewModelTests.swift
bozidarsevo Nov 22, 2024
a037029
Update WooAddCustomPackageView.swift
bozidarsevo Nov 22, 2024
a8368a2
Update WooShippingAddCustomPackageViewModelTests.swift
bozidarsevo Nov 22, 2024
cc3ec75
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Nov 22, 2024
55cf241
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Nov 25, 2024
187c375
Update after merge conflict
bozidarsevo Nov 25, 2024
6e8ea7c
Update WooShippingAddCustomPackageViewModel.swift
bozidarsevo Nov 25, 2024
ac1f13d
Load store options in WooShippingCreateLabelsViewModel
bozidarsevo Nov 27, 2024
893a098
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Nov 27, 2024
4d4499c
Update WooShippingAddCustomPackageViewModel.swift
bozidarsevo Nov 27, 2024
c046426
Update WooShippingCreateLabelsView.swift
bozidarsevo Nov 27, 2024
f592b08
Update WooShippingCreateLabelsViewModel.swift
bozidarsevo Nov 27, 2024
9d0a13d
Update WooShippingAddCustomPackageViewModelTests.swift
bozidarsevo Nov 27, 2024
38ceb16
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Nov 27, 2024
94a57cb
Remove packagesRepository from the model
bozidarsevo Nov 27, 2024
e12987f
Update WooShippingAddCustomPackageViewModelTests.swift
bozidarsevo Nov 27, 2024
21c746d
[Shipping labels] Custom packages model update (#14540)
bozidarsevo Nov 27, 2024
5433fc5
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Nov 28, 2024
7327882
Update WooShippingCreateLabelsView.swift
bozidarsevo Nov 28, 2024
1aaccc0
Update WooShippingCreateLabelsView.swift
bozidarsevo Nov 28, 2024
bc134ba
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Nov 28, 2024
5c3fbfb
Use units directly in WooShippingAddCustomPackageViewModel
bozidarsevo Nov 29, 2024
7eeb80e
Updates to store options loading logic
bozidarsevo Nov 29, 2024
a910f78
Update WooShippingAddCustomPackageViewModel.swift
bozidarsevo Nov 29, 2024
c17af3e
Update WooShippingPackageAndRatePlaceholder.swift
bozidarsevo Nov 29, 2024
b500b78
Remove not needed callback
bozidarsevo Nov 29, 2024
66705df
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Nov 29, 2024
103ba97
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Nov 29, 2024
191901b
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Dec 2, 2024
31b1e6f
Update WooShippingPackageAndRatePlaceholder.swift
bozidarsevo Dec 2, 2024
64c956b
Update WooShippingPackageAndRatePlaceholder.swift
bozidarsevo Dec 2, 2024
04fcea8
Merge branch 'trunk' into issue/14484-account-settings-for-UI-custom-…
bozidarsevo Dec 2, 2024
7a41f8c
Update WooShippingPackageAndRatePlaceholder.swift
bozidarsevo Dec 2, 2024
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
@@ -1,4 +1,5 @@
import SwiftUI
import enum Yosemite.WooShippingAction
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: This import isn't needed in the view here


struct WooAddCustomPackageView: View {
enum Constants {
Expand Down Expand Up @@ -62,12 +63,12 @@ struct WooAddCustomPackageView: View {
VStack {
AdaptiveStack(spacing: 8) {
ForEach(WooShippingPackageUnitType.dimensionUnits, id: \.self) { dimensionUnit in
unitInputView(for: dimensionUnit, unit: viewModel.dimensionsUnit)
unitInputView(for: dimensionUnit, unit: viewModel.storeOptions.dimensionUnit)
}
}
// showing weight input only if we are saving the template
if viewModel.showSaveTemplate {
unitInputView(for: WooShippingPackageUnitType.weight, unit: viewModel.weightUnit)
unitInputView(for: WooShippingPackageUnitType.weight, unit: viewModel.storeOptions.weightUnit)
}
}
.toolbar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Yosemite
final class WooShippingAddCustomPackageViewModel: ObservableObject {
private let stores: StoresManager
private let siteID: Int64
let storeOptions: ShippingLabelStoreOptions

// Holds values for all dimension input fields.
// Using a dictionary so we can easily add/remove new types
Expand All @@ -14,24 +15,15 @@ final class WooShippingAddCustomPackageViewModel: ObservableObject {
// Holds value for toggle that determines if we are showing button for saving the template
@Published var showSaveTemplate: Bool = false
@Published var packageTemplateName: String = ""
// The dimension unit used in the store (e.g. "in")
let dimensionsUnit: String
// The weight unit used in the store (e.g. "kg")
let weightUnit: String
@Published var packagesRepository: WooShippingPackagesRepositoryProtocol

// MARK: Initialization

init(siteID: Int64 = ServiceLocator.stores.sessionManager.defaultStoreID ?? 0,
dimensionsUnit: String? = ServiceLocator.shippingSettingsService.dimensionUnit,
weightUnit: String? = ServiceLocator.shippingSettingsService.weightUnit,
stores: StoresManager = ServiceLocator.stores,
packagesRepository: WooShippingPackagesRepositoryProtocol) {
self.dimensionsUnit = dimensionsUnit ?? ""
self.weightUnit = weightUnit ?? ""
storeOptions: ShippingLabelStoreOptions,
stores: StoresManager = ServiceLocator.stores) {
self.storeOptions = storeOptions
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: What do you think of keeping the dimensionsUnit and weightUnit properties and just using the ShippingLabelStoreOptions to set those values? No worries if you want to leave it like this, but the other approach felt a little clearer to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

After reading through the changes in WooShippingCreateLabelsViewModel I'm more inclined to keep these properties and the init parameters the same here. That way we only need to pass in the data that we're actually using, which can simplify the fallbacks we need to set if the request to load the store options fails. Just a thought, not a blocking suggestion!

self.stores = stores
self.siteID = siteID
self.packagesRepository = packagesRepository
}

// Field values are invalid if one of them is empty
Expand All @@ -52,15 +44,15 @@ final class WooShippingAddCustomPackageViewModel: ObservableObject {
return validFieldsCount != keysToCheck.count
}

private var packageDataFromCurrentData: WooShippingPackageDataRepresentable {
private var packageDataFromCurrentData: WooShippingPackageDataRepresentable? {
return WooShippingPackageData(id: UUID().uuidString,
name: packageTemplateName,
length: fieldValues[.length] ?? "",
width: fieldValues[.width] ?? "",
height: fieldValues[.height] ?? "",
dimensionsUnit: dimensionsUnit,
dimensionsUnit: storeOptions.dimensionUnit,
weight: fieldValues[.weight] ?? "",
weightUnit: weightUnit,
weightUnit: storeOptions.weightUnit,
source: .custom,
packageType: packageType.rawValue)
}
Expand Down Expand Up @@ -97,11 +89,39 @@ final class WooShippingAddCustomPackageViewModel: ObservableObject {
return .failure(WooShippingAddCustomPackageViewModel.Error.packageDataNotValid)
}

let result = await packagesRepository.saveCustomPackage(packageData,
dimensionsUnit: dimensionsUnit,
weightUnit: weightUnit,
siteID: siteID,
stores: stores)
let customPackage = WooShippingCustomPackage(id: "",
name: packageData.name,
rawType: packageData.packageType,
dimensions: "\(packageData.length) x \(packageData.width) x \(packageData.height)",
boxWeight: Double(packageData.weight) ?? 0)
let result: Result<WooShippingPackageDataRepresentable, Error> = await withCheckedContinuation { continuation in
let action = WooShippingAction.createPackage(siteID: siteID,
customPackage: customPackage,
predefinedOption: nil) { [weak self] result in
switch result {
case let .success(packages):
guard let self, let savedPackage = packages.customPackages.first(where: { $0.name == customPackage.name }) else {
return continuation.resume(returning: .failure(WooShippingAddCustomPackageViewModel.Error.failedSavingTemplate))
}
let packageData = WooShippingPackageData(id: savedPackage.id,
name: savedPackage.name,
length: savedPackage.getLength().description,
width: savedPackage.getWidth().description,
height: savedPackage.getHeight().description,
dimensionsUnit: storeOptions.dimensionUnit,
weight: savedPackage.boxWeight.description,
weightUnit: storeOptions.weightUnit,
source: .custom,
packageType: savedPackage.rawType)
continuation.resume(returning: .success(packageData))
case let .failure(error):
DDLogError("⛔️ Error saving custom package with WCShip: \(error)")
continuation.resume(returning: .failure(WooShippingAddCustomPackageViewModel.Error.failedSavingTemplate))
}
}
stores.dispatch(action)
}

switch result {
case .success(let success):
return .success(success)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SwiftUI
import struct Yosemite.ShippingLabelStoreOptions

struct WooShippingAddPackageView: View {
enum PackageProviderType: CaseIterable {
Expand All @@ -24,10 +25,11 @@ struct WooShippingAddPackageView: View {

let addPackageAction: (WooShippingPackageDataRepresentable) -> Void

init(packagesRepository: WooShippingPackagesRepository = WooShippingPackagesRepository.shared,
init(storeOptions: ShippingLabelStoreOptions,
packagesRepository: WooShippingPackagesRepository = WooShippingPackagesRepository.shared,
addPackageAction: @escaping (WooShippingPackageDataRepresentable) -> Void) {
self._packagesRepository = StateObject(wrappedValue: packagesRepository)
self._customPackageViewModel = StateObject(wrappedValue: WooShippingAddCustomPackageViewModel(packagesRepository: packagesRepository))
self._customPackageViewModel = StateObject(wrappedValue: WooShippingAddCustomPackageViewModel(storeOptions: storeOptions))
self.addPackageAction = addPackageAction
}
// MARK: - UI
Expand Down Expand Up @@ -134,7 +136,10 @@ struct WooShippingAddPackageUnitInputView: View {
}

#Preview {
WooShippingAddPackageView { _ in }
WooShippingAddPackageView(storeOptions: ShippingLabelStoreOptions(currencySymbol: "$",
dimensionUnit: "in",
weightUnit: "oz",
originCountry: "US")) { _ in }
}

extension WooShippingAddPackageView {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import SwiftUI
import struct Yosemite.ShippingLabelStoreOptions

struct WooShippingPackageAndRatePlaceholder: View {
@State private var showAddPackage: Bool = false
let storeOptions: ShippingLabelStoreOptions

var body: some View {
VStack(spacing: .zero) {
Button {
Expand All @@ -24,7 +27,7 @@ struct WooShippingPackageAndRatePlaceholder: View {
.padding(Layout.padding)
.roundedBorder(cornerRadius: Layout.borderCornerRadius, lineColor: Color(.border), lineWidth: Layout.borderLineWidth, dashed: true)
.sheet(isPresented: $showAddPackage) {
WooShippingAddPackageView { packageData in
WooShippingAddPackageView(storeOptions: storeOptions) { packageData in
// TODO: use packageData
showAddPackage = false
}
Expand Down Expand Up @@ -58,6 +61,9 @@ private extension WooShippingPackageAndRatePlaceholder {
}

#Preview {
WooShippingPackageAndRatePlaceholder()
WooShippingPackageAndRatePlaceholder(storeOptions: ShippingLabelStoreOptions(currencySymbol: "$",
dimensionUnit: "in",
weightUnit: "oz",
originCountry: "US"))
.padding()
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ struct WooShippingCreateLabelsView: View {
WooShippingServiceView(viewModel: shippingService)
.padding(.horizontal, -16)
} else {
WooShippingPackageAndRatePlaceholder()
if let storeOptions = viewModel.storeOptions {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is now much cleaner and easier to review 🥳
I put the check for the storeOptions just here so that just this view is not showed before we get storeOptions so later on we can do other changes if needed 🕺 @rachelmcr

WooShippingPackageAndRatePlaceholder(storeOptions: storeOptions)
}
else {
loadingView
Copy link
Contributor

Choose a reason for hiding this comment

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

Having a loading view here feels a little odd to me, especially because the data isn't needed for this placeholder view — it just needs to be passed down to its child views.

What do you think about setting the store options as an environment variable, instead? That way we could initialize this view without the storeOptions property necessarily being loaded yet, and once we have those options we can pass them to the views that need them. Or we could use some other approach to set/update the store options after initialization.

This would also allow us to use the fallbacks from ServiceLocator immediately, and then update them in place if we get fresh data from the backend.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We will still need to do pick a place where we want to trigger the fetching/loading the data. And in theory that loading could take a longer time and user could get to a state in UI that needs this data and we need to show that something will be there.

For example, I do not show any loading indicators and user gets to Add Package screen and Custom tab. I still do not have the store options therefor I do not have the dimension and weight units. Should I then show some indicator that something is loading? Because at that state we do not have all the data needed to show the UI and we cannot just show blank white screen.

If I use environment variable again we need to decide when will we trigger fetching/loading so it would basically be the same just a different implementation.

I can do it in a way that I trigger the initial load when we enter label creation flow and then if some part of the app needs the store options and they are not loaded yet I will show some loading indication there, basically showing the loading at the last possible state to avoid blocking user as little as possible.

I will see if I will do that change here or in a separate PR when I see how big of a change would it be.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated:

Basically loading store options (account settings) when we enter the label creation screen, but showing loading indicator only if I get to the part of the UI that needs the store options and they are not loaded. That way in most cases users will not even see the loading indicator. 😊

Updated screen recordings in description

Copy link
Contributor

Choose a reason for hiding this comment

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

For example, I do not show any loading indicators and user gets to Add Package screen and Custom tab. I still do not have the store options therefor I do not have the dimension and weight units. Should I then show some indicator that something is loading?

Why not use the dimension and weight units from ServiceLocator.shippingSettingsService by default? We can still trigger fetching/loading the account settings when the shipping label flow begins, and update those units in place if they are different from the default we've used.

}
}
}
.padding(16)
Expand Down Expand Up @@ -151,6 +156,28 @@ struct WooShippingCreateLabelsView: View {
}
}
}
.onAppear() {
guard viewModel.storeOptions == nil else { return }
viewModel.loadStoreOptions()
}
}

private var loadingView: some View {
HStack {
Spacer()
if viewModel.isLoadingStoreOptions {
ActivityIndicator(isAnimating: .constant(true), style: .large)
}
else {
Button {
viewModel.loadStoreOptions()
} label: {
Image(systemName: "arrow.trianglehead.counterclockwise")
}
}
Spacer()
}
.padding()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,40 @@ final class WooShippingCreateLabelsViewModel: ObservableObject {
/// If the label purchase is in progress.
@Published private(set) var isPurchasingLabel: Bool = false

@Published var storeOptions: ShippingLabelStoreOptions?
@Published var isLoadingStoreOptions: Bool = false

func loadStoreOptions() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Loading and storing the store options so we get fresh/live weight and dimensions units.

That property is then being passed to all UI in shipping labels flow

guard isLoadingStoreOptions == false,
let siteID = ServiceLocator.stores.sessionManager.defaultStoreID else { return }

isLoadingStoreOptions = true

let action = WooShippingAction.loadAccountSettings(siteID: siteID) { result in
switch result {
case .success(let settings):
self.storeOptions = settings.storeOptions
case .failure(let error):
DDLogError("⛔️ Error loading account settings: \(error)")
// fallback to store settings
let shippingSettingsService = ServiceLocator.shippingSettingsService
let currencySettings = ServiceLocator.currencySettings
let currencySymbol = currencySettings.symbol(from: currencySettings.currencyCode)
let originCountry = SiteAddress().countryCode.rawValue
if let dimensionUnit = shippingSettingsService.dimensionUnit,
let weightUnit = shippingSettingsService.weightUnit {
let fallbackStoreOptions = ShippingLabelStoreOptions(currencySymbol: currencySymbol,
dimensionUnit: dimensionUnit,
weightUnit: weightUnit,
originCountry: originCountry)
self.storeOptions = fallbackStoreOptions
}
}
self.isLoadingStoreOptions = false
}
ServiceLocator.stores.dispatch(action)
}

/// Closure to execute after the label is successfully purchased.
let onLabelPurchase: ((_ markOrderComplete: Bool) -> Void)?

Expand Down
Loading