-
Notifications
You must be signed in to change notification settings - Fork 115
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
Initial changes for saving product images upload statuses in UserDefaults #15196
base: trunk
Are you sure you want to change the base?
Changes from all commits
358daa8
992cc5c
b7b2078
a804b1e
e3c87b8
66c6694
6c678cf
d506946
51a1cfd
70e0fa9
27a1146
1f96482
c6be542
561582a
309d947
c84cef6
1344637
f5803bb
1d85fd2
e012096
67dd20a
aafaa19
0c362dc
6df709a
666adbe
a66c800
d2eb66d
9659be1
6ea99b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import Foundation | ||
import Photos | ||
import UIKit | ||
|
||
/// The status of a Product image. | ||
/// | ||
|
||
public enum ProductImageStatus: Equatable, Codable { | ||
/// An image asset is being uploaded. | ||
/// | ||
case uploading(asset: ProductImageAssetType, siteID: Int64, productID: ProductOrVariationID) | ||
|
||
/// The Product image exists remotely. | ||
/// | ||
case remote(image: ProductImage, siteID: Int64, productID: ProductOrVariationID) | ||
|
||
/// An image asset upload failed. | ||
/// | ||
case uploadFailure(asset: ProductImageAssetType, error: Error, siteID: Int64, productID: ProductOrVariationID) | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case type | ||
case asset | ||
case image | ||
case error | ||
case siteID | ||
case productID | ||
case errorDomain | ||
case errorCode | ||
case errorUserInfo | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
let typeString = try container.decode(String.self, forKey: .type) | ||
|
||
switch typeString { | ||
case "uploading": | ||
let asset = try container.decode(ProductImageAssetType.self, forKey: .asset) | ||
let sID = try container.decode(Int64.self, forKey: .siteID) | ||
let pID = try container.decode(ProductOrVariationID.self, forKey: .productID) | ||
self = .uploading(asset: asset, siteID: sID, productID: pID) | ||
case "remote": | ||
let image = try container.decode(ProductImage.self, forKey: .image) | ||
let sID = try container.decode(Int64.self, forKey: .siteID) | ||
let pID = try container.decode(ProductOrVariationID.self, forKey: .productID) | ||
self = .remote(image: image, siteID: sID, productID: pID) | ||
case "uploadFailure": | ||
let asset = try container.decode(ProductImageAssetType.self, forKey: .asset) | ||
let sID = try container.decode(Int64.self, forKey: .siteID) | ||
let pID = try container.decode(ProductOrVariationID.self, forKey: .productID) | ||
let domain = try container.decode(String.self, forKey: .errorDomain) | ||
let code = try container.decode(Int.self, forKey: .errorCode) | ||
let userInfo = try container.decode([String: String]?.self, forKey: .errorUserInfo) | ||
self = .uploadFailure(asset: asset, error: NSError(domain: domain, code: code, userInfo: userInfo), siteID: sID, productID: pID) | ||
default: | ||
throw DecodingError.dataCorruptedError(forKey: .type, | ||
in: container, | ||
debugDescription: "Invalid type value: \(typeString)") | ||
} | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
switch self { | ||
case .uploading(let asset, let siteID, let productID): | ||
try container.encode("uploading", forKey: .type) | ||
try container.encode(asset, forKey: .asset) | ||
try container.encode(siteID, forKey: .siteID) | ||
try container.encode(productID, forKey: .productID) | ||
case .remote(let image, let siteID, let productID): | ||
try container.encode("remote", forKey: .type) | ||
try container.encode(image, forKey: .image) | ||
try container.encode(siteID, forKey: .siteID) | ||
try container.encode(productID, forKey: .productID) | ||
case .uploadFailure(let asset, let error, let siteID, let productID): | ||
try container.encode("uploadFailure", forKey: .type) | ||
try container.encode(asset, forKey: .asset) | ||
try container.encode(siteID, forKey: .siteID) | ||
try container.encode(productID, forKey: .productID) | ||
|
||
let errorMessage = error as NSError | ||
try container.encode(errorMessage.domain, forKey: .errorDomain) | ||
try container.encode(errorMessage.code, forKey: .errorCode) | ||
try container.encode(errorMessage.userInfo as? [String: String], forKey: .errorUserInfo) | ||
} | ||
} | ||
|
||
public static func == (lhs: Self, rhs: Self) -> Bool { | ||
switch (lhs, rhs) { | ||
case let (.uploading(lAsset, lSiteID, lProductID), .uploading(rAsset, rSiteID, rProductID)): | ||
return lAsset == rAsset && lSiteID == rSiteID && lProductID == rProductID | ||
case let (.remote(lImage, lSiteID, lProductID), .remote(rImage, rSiteID, rProductID)): | ||
return lImage == rImage && lSiteID == rSiteID && lProductID == rProductID | ||
case let (.uploadFailure(lAsset, lError, lSiteID, lProductID), .uploadFailure(rAsset, rError, rSiteID, rProductID)): | ||
return lAsset == rAsset && | ||
(lError as NSError) == (rError as NSError) && | ||
lSiteID == rSiteID && | ||
lProductID == rProductID | ||
default: | ||
return false | ||
} | ||
} | ||
} | ||
|
||
/// The type of product image asset. | ||
public enum ProductImageAssetType: Equatable, Codable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you added Codable conformance to |
||
/// `PHAsset` from device photo library or camera capture. | ||
case phAsset(asset: PHAsset) | ||
|
||
/// `UIImage` from image processing. The filename and alt text need to be provided separately. | ||
case uiImage(image: UIImage, filename: String?, altText: String?) | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case type | ||
case asset // For phAsset: localIdentifier string | ||
case imageData // For uiImage: base64 encoded image data | ||
case filename | ||
case altText | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
let typeString = try container.decode(String.self, forKey: .type) | ||
switch typeString { | ||
case "phAsset": | ||
let localIdentifier = try container.decode(String.self, forKey: .asset) | ||
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil) | ||
guard let asset = fetchResult.firstObject else { | ||
throw DecodingError.dataCorruptedError(forKey: .asset, | ||
in: container, | ||
debugDescription: "No PHAsset found with localIdentifier \(localIdentifier)") | ||
} | ||
self = .phAsset(asset: asset) | ||
case "uiImage": | ||
let base64String = try container.decode(String.self, forKey: .imageData) | ||
guard let imageData = Data(base64Encoded: base64String), | ||
let image = UIImage(data: imageData) else { | ||
throw DecodingError.dataCorruptedError(forKey: .imageData, | ||
in: container, | ||
debugDescription: "Invalid image data") | ||
} | ||
let filename = try container.decodeIfPresent(String.self, forKey: .filename) | ||
let altText = try container.decodeIfPresent(String.self, forKey: .altText) | ||
self = .uiImage(image: image, filename: filename, altText: altText) | ||
default: | ||
throw DecodingError.dataCorruptedError(forKey: .type, | ||
in: container, | ||
debugDescription: "Unknown type \(typeString)") | ||
} | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
switch self { | ||
case .phAsset(let asset): | ||
try container.encode("phAsset", forKey: .type) | ||
try container.encode(asset.localIdentifier, forKey: .asset) | ||
case .uiImage(let image, let filename, let altText): | ||
try container.encode("uiImage", forKey: .type) | ||
guard let imageData = image.pngData() else { | ||
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "Unable to encode UIImage as PNG") | ||
throw EncodingError.invalidValue(image, context) | ||
} | ||
let base64String = imageData.base64EncodedString() | ||
try container.encode(base64String, forKey: .imageData) | ||
try container.encode(filename, forKey: .filename) | ||
try container.encode(altText, forKey: .altText) | ||
} | ||
} | ||
|
||
public static func == (lhs: ProductImageAssetType, rhs: ProductImageAssetType) -> Bool { | ||
switch (lhs, rhs) { | ||
case (.phAsset(let lAsset), .phAsset(let rAsset)): | ||
return lAsset.localIdentifier == rAsset.localIdentifier | ||
case (.uiImage(let lImage, let lFilename, let lAltText), | ||
.uiImage(let rImage, let rFilename, let rAltText)): | ||
return lImage.pngData() == rImage.pngData() && | ||
lFilename == rFilename && | ||
lAltText == rAltText | ||
default: | ||
return false | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/// Identifiable data about a product or product variation. | ||
public enum ProductOrVariationID: Equatable, Hashable, Codable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you added Codable conformance to |
||
case product(id: Int64) | ||
case variation(productID: Int64, variationID: Int64) | ||
|
||
/// Returns the product ID for product type and variation ID for variation type. | ||
public var id: Int64 { | ||
switch self { | ||
case .product(let id): | ||
return id | ||
case .variation(_, let variationID): | ||
return variationID | ||
} | ||
} | ||
|
||
private enum CodingKeys: String, CodingKey { | ||
case type | ||
case id | ||
case productID | ||
case variationID | ||
} | ||
|
||
private enum CaseType: String, Codable { | ||
case product | ||
case variation | ||
} | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
let caseType = try container.decode(CaseType.self, forKey: .type) | ||
switch caseType { | ||
case .product: | ||
let id = try container.decode(Int64.self, forKey: .id) | ||
self = .product(id: id) | ||
case .variation: | ||
let productID = try container.decode(Int64.self, forKey: .productID) | ||
let variationID = try container.decode(Int64.self, forKey: .variationID) | ||
self = .variation(productID: productID, variationID: variationID) | ||
} | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.container(keyedBy: CodingKeys.self) | ||
switch self { | ||
case .product(let id): | ||
try container.encode(CaseType.product, forKey: .type) | ||
try container.encode(id, forKey: .id) | ||
case .variation(let productID, let variationID): | ||
try container.encode(CaseType.variation, forKey: .type) | ||
try container.encode(productID, forKey: .productID) | ||
try container.encode(variationID, forKey: .variationID) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you added Codable conformance to
ProductImageStatus
, please also add tests to confirm that both the encoding and decoding work as expected.