From 2afb9057121b931b745e734c3456b77bdbf5bbb2 Mon Sep 17 00:00:00 2001 From: Henadzi Rabkin Date: Thu, 28 Dec 2023 17:12:56 +0100 Subject: [PATCH] Some more issues fixed --- .../ExampleCodeFromScanner/ContentView.swift | 2 +- Example/ExampleWithCode/ContentView.swift | 4 +- Sources/Extensions/Extensions.swift | 21 +++++----- Sources/Model/OFF/Product.swift | 38 ++++++++++++++++++- Sources/ProductPage.swift | 8 ++-- Sources/ViewModels/ProductPageConfig.swift | 8 +++- .../Cropper/ImageCropper.swift | 7 ++-- 7 files changed, 65 insertions(+), 23 deletions(-) diff --git a/Example/ExampleCodeFromScanner/ContentView.swift b/Example/ExampleCodeFromScanner/ContentView.swift index 6c3f2f4..7adc3f9 100644 --- a/Example/ExampleCodeFromScanner/ContentView.swift +++ b/Example/ExampleCodeFromScanner/ContentView.swift @@ -26,7 +26,7 @@ struct ContentView: View { BarcodeScannerScreen(barcode: $barcode, isCapturing: $isScanning).ignoresSafeArea(.all) .navigationDestination(isPresented: $isEditing, destination: { ProductPage(barcode: barcode) { uploadedProduct in - print(uploadedProduct ?? "") + print(uploadedProduct?.json() ?? "returned product is nil") }.onDisappear() { resetState() } diff --git a/Example/ExampleWithCode/ContentView.swift b/Example/ExampleWithCode/ContentView.swift index 5b3363b..472a902 100644 --- a/Example/ExampleWithCode/ContentView.swift +++ b/Example/ExampleWithCode/ContentView.swift @@ -32,8 +32,8 @@ struct ContentView: View { }.padding() // Added for testing that editor is loaded with NavigatorView NavigationLink("Check") { - ProductPage(barcode: self.barcode) { product in - print(product.json()) + ProductPage(barcode: self.barcode) { uploadedProduct in + print(uploadedProduct?.json() ?? "returned product is nil") } }.disabled(!isValidBarcode) Spacer() diff --git a/Sources/Extensions/Extensions.swift b/Sources/Extensions/Extensions.swift index 40e7f4c..7d05773 100644 --- a/Sources/Extensions/Extensions.swift +++ b/Sources/Extensions/Extensions.swift @@ -111,22 +111,23 @@ extension UIImage { } } -public extension [String: String]? { - - public func json() -> String { - guard let dictionary = self else { - return "Product is nil" - } - +public extension Product { + func json() -> String { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted do { - let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) - return String(data: data, encoding: .utf8) ?? "Empty" + let jsonData = try encoder.encode(self) + if let jsonString = String(data: jsonData, encoding: .utf8) { + return jsonString + } + return "Couldn't encode json string from the data" } catch { - return "Error serializing product: \(error)" + return "Error while encoding Product to JSON: \(error)" } } } + public extension [String: String] { public func json() -> String { diff --git a/Sources/Model/OFF/Product.swift b/Sources/Model/OFF/Product.swift index 48ed56a..f3f12af 100644 --- a/Sources/Model/OFF/Product.swift +++ b/Sources/Model/OFF/Product.swift @@ -21,7 +21,7 @@ public enum DataFor: String { } } -public struct Product: Decodable, Equatable { +public struct Product: Codable, Equatable { public static func == (lhs: Product, rhs: Product) -> Bool { return lhs.code == rhs.code && @@ -96,6 +96,37 @@ public struct Product: Decodable, Equatable { self.lang = OpenFoodFactsLanguage.UNDEFINED } } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(code, forKey: .code) + try container.encodeIfPresent(brands, forKey: .brands) + try container.encodeIfPresent(productName, forKey: .productName) + try container.encodeIfPresent(quantity, forKey: .quantity) + try container.encodeIfPresent(servingSize, forKey: .servingSize) + try container.encodeIfPresent(dataPer, forKey: .dataPer) + try container.encodeIfPresent(categories, forKey: .categories) + try container.encodeIfPresent(imageFront, forKey: .imageFront) + try container.encodeIfPresent(imageIngredients, forKey: .imageIngredients) + try container.encodeIfPresent(imageNutrition, forKey: .imageNutrition) + + if let nutriments = self.nutriments { + var nutrimentsContainer = container.nestedContainer(keyedBy: AnyCodingKey.self, forKey: .nutriments) + for (key, value) in nutriments { + let key = AnyCodingKey(stringValue: key)! + switch value { + case let doubleValue as Double: + try nutrimentsContainer.encode(doubleValue, forKey: key) + case let stringValue as String: + try nutrimentsContainer.encode(stringValue, forKey: key) + default: + break + } + } + } + + try container.encodeIfPresent(lang?.info.code, forKey: .lang) + } } private struct AnyCodingKey: CodingKey { @@ -109,4 +140,9 @@ private struct AnyCodingKey: CodingKey { init?(intValue: Int) { return nil } + + init(_ key: CodingKey) { + self.stringValue = key.stringValue + self.intValue = key.intValue + } } diff --git a/Sources/ProductPage.swift b/Sources/ProductPage.swift index d79643d..b5bde01 100644 --- a/Sources/ProductPage.swift +++ b/Sources/ProductPage.swift @@ -85,10 +85,6 @@ public struct ProductPage: View { errorMessage: $pageConfig.errorMessage ).ignoresSafeArea() }) - .onChange(of: pageConfig.submittedProduct) { newValue in - self.onUploadingDone(newValue) - dismiss() - } .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { @@ -112,6 +108,10 @@ public struct ProductPage: View { } } .navigationBarBackButtonHidden() + .onChange(of: pageConfig.submittedProduct) { newValue in + self.onUploadingDone(newValue) + dismiss() + } .alert(item: $pageConfig.errorMessage, content: { alert in Alert(title: Text(alert.title), message: Text(alert.message), dismissButton: .cancel(Text("OK"), action: { self.pageConfig.errorMessage = nil diff --git a/Sources/ViewModels/ProductPageConfig.swift b/Sources/ViewModels/ProductPageConfig.swift index f70d9c9..a88bd77 100644 --- a/Sources/ViewModels/ProductPageConfig.swift +++ b/Sources/ViewModels/ProductPageConfig.swift @@ -64,6 +64,8 @@ final class ProductPageConfig: ObservableObject { return pageType == .new } + // FIXME: move MainActor on class itself + func fetchData(barcode: String) async { await MainActor.run { @@ -88,6 +90,7 @@ final class ProductPageConfig: ObservableObject { self.isInitialised = true self.pageType = pageType } + // FIXME: find a way to show animation states without such workarounds try await Task.sleep(nanoseconds: 1_000_000_000 * UInt64(PageOverlay.completedAnimDuration)) await MainActor.run { self.pageState = .productDetails @@ -184,17 +187,18 @@ final class ProductPageConfig: ObservableObject { self.pageState = .loading } - async let _ = sendAllImages(barcode: barcode) + await sendAllImages(barcode: barcode) do { let productBody = try await composeProductBody(barcode: barcode) try await OpenFoodAPIClient.shared.saveProduct(product: productBody) + let productResponse = try await OpenFoodAPIClient.shared.getProduct(config: ProductQueryConfiguration(barcode: barcode)) await MainActor.run { self.pageState = .completed } try await Task.sleep(nanoseconds: 1_000_000_000 * UInt64(PageOverlay.completedAnimDuration)) await MainActor.run { self.pageState = ProductPageState.productDetails - self.submittedProduct = productBody.convertToProduct() + self.submittedProduct = productResponse.product } } catch { await MainActor.run { diff --git a/Sources/Views/ImagePickerCropper/Cropper/ImageCropper.swift b/Sources/Views/ImagePickerCropper/Cropper/ImageCropper.swift index 4a84493..8d60d27 100644 --- a/Sources/Views/ImagePickerCropper/Cropper/ImageCropper.swift +++ b/Sources/Views/ImagePickerCropper/Cropper/ImageCropper.swift @@ -34,15 +34,16 @@ class Coordinator: NSObject, CropViewControllerDelegate { } func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { - withAnimation { - parent.isPresented = false - } + // FIXME: it doesn't work, image has original size if (!image.isPictureBigEnough()) { parent.errorMessage = ErrorAlert(message: "Invalid image", title: "The image is too small! Minimum WxH 640x160") } else { self.parent.image = image } + withAnimation { + parent.isPresented = false + } } func cropViewController(_ cropViewController: CropViewController, didFinishCancelled cancelled: Bool) {