Skip to content
This repository has been archived by the owner on Sep 5, 2023. It is now read-only.

Commit

Permalink
offline products consultation. Fix #35 (#249)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippeauriach authored and teolemon committed Mar 5, 2019
1 parent f6c44f7 commit f9f4a75
Show file tree
Hide file tree
Showing 20 changed files with 631 additions and 41 deletions.
3 changes: 2 additions & 1 deletion Cartfile
Expand Up @@ -13,4 +13,5 @@ github "PiXeL16/IBLocalizable" "Swift4"
github "realm/realm-cocoa"
github "SnapKit/SnapKit"
github "scenee/FloatingPanel" "v1.3.5"
github "TimOliver/TOCropViewController"
github "TimOliver/TOCropViewController"
github "marmelroy/Zip" "1.1.0"
2 changes: 2 additions & 0 deletions Cartfile.resolved
Expand Up @@ -2,6 +2,7 @@ github "Alamofire/Alamofire" "4.8.1"
github "AliSoftware/OHHTTPStubs" "4dc6f36375f78c0b3cfe58d90bb8a4e21df5196e"
github "Daltron/NotificationBanner" "2.0.1"
github "DaveWoodCom/XCGLogger" "6.1.0"
github "Flinesoft/HandySwift" "2.8.0"
github "PiXeL16/IBLocalizable" "a0d7a8fab4cec66b592ac83f9efbc6f30bd21a9b"
github "Quick/Nimble" "v7.3.1"
github "Quick/Quick" "v1.3.2"
Expand All @@ -13,6 +14,7 @@ github "cbpowell/MarqueeLabel" "3.2.0"
github "hackiftekhar/IQKeyboardManager" "v5.0.6"
github "httpswift/swifter" "294dc8eaa7aed12f8695f43a5749b5c8f0f175b7"
github "kishikawakatsumi/KeychainAccess" "v3.1.2"
github "marmelroy/Zip" "1.1.0"
github "onevcat/Kingfisher" "5.1.0"
github "realm/realm-cocoa" "v3.13.1"
github "scenee/FloatingPanel" "v1.3.5"
Expand Down
22 changes: 22 additions & 0 deletions OpenFoodFacts.xcodeproj/project.pbxproj
Expand Up @@ -10,6 +10,7 @@
3000855E2207A3A500DC3896 /* TaxonomiesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3000855D2207A3A500DC3896 /* TaxonomiesService.swift */; };
300085612207AC0700DC3896 /* Additive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300085602207AC0700DC3896 /* Additive.swift */; };
300085632208777900DC3896 /* OFFUrlsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300085622208777900DC3896 /* OFFUrlsHelper.swift */; };
3010E25F222EF42B00313ADB /* InitialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3010E25E222EF42B00313ADB /* InitialView.swift */; };
302525E92223044E00C2C830 /* AllergensAlertsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302525E72223044E00C2C830 /* AllergensAlertsTableViewController.swift */; };
302525EC222307A200C2C830 /* RealmUserPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302525EB222307A200C2C830 /* RealmUserPreferences.swift */; };
302525EF222311A000C2C830 /* SelectAllergenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302525ED222311A000C2C830 /* SelectAllergenViewController.swift */; };
Expand All @@ -22,6 +23,10 @@
30270C322226EAD000E6973D /* SummaryFooterCellController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 30270C312226EAD000E6973D /* SummaryFooterCellController.xib */; };
30270C342226F4A000E6973D /* operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30270C332226F4A000E6973D /* operators.swift */; };
30270C37222743D200E6973D /* EnvironmentImpactTableFormTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30270C36222743D200E6973D /* EnvironmentImpactTableFormTableViewController.swift */; };
3030EB84222E8290005A6169 /* OfflineProductsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3030EB83222E8290005A6169 /* OfflineProductsService.swift */; };
3030EB8A222E9202005A6169 /* RealmOfflineProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3030EB89222E9202005A6169 /* RealmOfflineProduct.swift */; };
3030EB8B222E992B005A6169 /* Zip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3030EB85222E8A08005A6169 /* Zip.framework */; };
3030EB8D222E9954005A6169 /* CSVStreamReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3030EB8C222E9954005A6169 /* CSVStreamReader.swift */; };
303C279D2201AB6B00159961 /* ScanProductSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303C279C2201AB6B00159961 /* ScanProductSummaryView.swift */; };
303C279F2201AB7B00159961 /* ScanProductSummaryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 303C279E2201AB7B00159961 /* ScanProductSummaryView.xib */; };
303C27A12201ABC300159961 /* ManualBarcodeInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303C27A02201ABC300159961 /* ManualBarcodeInputView.swift */; };
Expand Down Expand Up @@ -295,6 +300,7 @@
3000855D2207A3A500DC3896 /* TaxonomiesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaxonomiesService.swift; sourceTree = "<group>"; };
300085602207AC0700DC3896 /* Additive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Additive.swift; sourceTree = "<group>"; };
300085622208777900DC3896 /* OFFUrlsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OFFUrlsHelper.swift; sourceTree = "<group>"; };
3010E25E222EF42B00313ADB /* InitialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialView.swift; sourceTree = "<group>"; };
302525E72223044E00C2C830 /* AllergensAlertsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllergensAlertsTableViewController.swift; sourceTree = "<group>"; };
302525EB222307A200C2C830 /* RealmUserPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmUserPreferences.swift; sourceTree = "<group>"; };
302525ED222311A000C2C830 /* SelectAllergenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectAllergenViewController.swift; sourceTree = "<group>"; };
Expand All @@ -307,6 +313,10 @@
30270C312226EAD000E6973D /* SummaryFooterCellController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SummaryFooterCellController.xib; sourceTree = "<group>"; };
30270C332226F4A000E6973D /* operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = operators.swift; sourceTree = "<group>"; };
30270C36222743D200E6973D /* EnvironmentImpactTableFormTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentImpactTableFormTableViewController.swift; sourceTree = "<group>"; };
3030EB83222E8290005A6169 /* OfflineProductsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineProductsService.swift; sourceTree = "<group>"; };
3030EB85222E8A08005A6169 /* Zip.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Zip.framework; path = Carthage/Build/iOS/Zip.framework; sourceTree = "<group>"; };
3030EB89222E9202005A6169 /* RealmOfflineProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmOfflineProduct.swift; sourceTree = "<group>"; };
3030EB8C222E9954005A6169 /* CSVStreamReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSVStreamReader.swift; sourceTree = "<group>"; };
303C279C2201AB6B00159961 /* ScanProductSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanProductSummaryView.swift; sourceTree = "<group>"; };
303C279E2201AB7B00159961 /* ScanProductSummaryView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ScanProductSummaryView.xib; sourceTree = "<group>"; };
303C27A02201ABC300159961 /* ManualBarcodeInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualBarcodeInputView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -919,6 +929,7 @@
9526F8FB1FE1C5230008E1CC /* Crashlytics.framework in Frameworks */,
307BBEB921FA16B100E2DF9D /* FloatingPanel.framework in Frameworks */,
30DB17F522122E4A0010EE6F /* TOCropViewController.framework in Frameworks */,
3030EB8B222E992B005A6169 /* Zip.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1130,6 +1141,8 @@
children = (
957094AE1EA124E800268236 /* ProductService.swift */,
3000855D2207A3A500DC3896 /* TaxonomiesService.swift */,
3030EB83222E8290005A6169 /* OfflineProductsService.swift */,
3030EB8C222E9954005A6169 /* CSVStreamReader.swift */,
);
path = Network;
sourceTree = "<group>";
Expand Down Expand Up @@ -1176,6 +1189,7 @@
children = (
955BAFFD1FF828540046F419 /* RealmPendingUploadItem.swift */,
302525EB222307A200C2C830 /* RealmUserPreferences.swift */,
3030EB89222E9202005A6169 /* RealmOfflineProduct.swift */,
);
path = Realm;
sourceTree = "<group>";
Expand Down Expand Up @@ -1307,6 +1321,7 @@
956FF8B01F1FF0CA0069D678 /* StateViews */ = {
isa = PBXGroup;
children = (
3010E25E222EF42B00313ADB /* InitialView.swift */,
95890AB41F967DE200085B20 /* InitialView.xib */,
956FF8B31F1FF0CA0069D678 /* EmptyView.swift */,
956FF8B41F1FF0CA0069D678 /* ErrorView.swift */,
Expand Down Expand Up @@ -1637,6 +1652,7 @@
95C265231E96D5C2004212EC /* Frameworks */ = {
isa = PBXGroup;
children = (
3030EB85222E8A08005A6169 /* Zip.framework */,
30DB17F422122E4A0010EE6F /* TOCropViewController.framework */,
307BBEB821FA16B100E2DF9D /* FloatingPanel.framework */,
9526F8F91FE1C5230008E1CC /* Crashlytics.framework */,
Expand Down Expand Up @@ -2232,6 +2248,7 @@
"$(SRCROOT)/Carthage/Build/iOS/RealmSwift.framework",
"$(SRCROOT)/Carthage/Build/iOS/FloatingPanel.framework",
"$(SRCROOT)/Carthage/Build/iOS/TOCropViewController.framework",
"$(SRCROOT)/Carthage/Build/iOS/Zip.framework",
);
name = Carthage;
outputPaths = (
Expand All @@ -2253,6 +2270,7 @@
"$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RealmSwift.framework",
"$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/FloatingPanel.framework",
"$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/TOCropViewController.framework",
"$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Zip.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
Expand Down Expand Up @@ -2344,6 +2362,8 @@
958F33441F361A9D005269C5 /* IngredientsHeaderCellController.swift in Sources */,
956FF9551F1FF0CA0069D678 /* ProductTableViewCell.swift in Sources */,
95781BC31EC3A898003E3256 /* DoubleTransform.swift in Sources */,
3010E25F222EF42B00313ADB /* InitialView.swift in Sources */,
3030EB84222E8290005A6169 /* OfflineProductsService.swift in Sources */,
9587DF731F84056F0069F0A6 /* PictureViewModel.swift in Sources */,
95781BBC1EC3A898003E3256 /* OFFReadAPIKeysJSON.swift in Sources */,
95A566141FD6037800C997C8 /* LocalizableTextField.swift in Sources */,
Expand Down Expand Up @@ -2413,9 +2433,11 @@
957CBEAC1F323B1F00A1B398 /* FormTableViewController.swift in Sources */,
957CBEC41F33B36400A1B398 /* SummaryFormTableViewController.swift in Sources */,
74C59E8B203FB9E2006C456F /* SharingManager.swift in Sources */,
3030EB8A222E9202005A6169 /* RealmOfflineProduct.swift in Sources */,
95D1D29C1FFC33B600595EA1 /* ShortcutParser.swift in Sources */,
9526F8F51FDF2A8B0008E1CC /* DataManagerClient.swift in Sources */,
958F335B1F37809A005269C5 /* PictureCallToActionView.swift in Sources */,
3030EB8D222E9954005A6169 /* CSVStreamReader.swift in Sources */,
95D060DD1F609DF70052012D /* LoadingCell.swift in Sources */,
95DB3BEA1FDD97D800E83B76 /* HistoryTableViewController.swift in Sources */,
958F334D1F377831005269C5 /* NutritionTableHeaderCellController.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Sources/AppDelegate.swift
Expand Up @@ -62,7 +62,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

private func configureRealm() {
let config = Realm.Configuration(
schemaVersion: 21
schemaVersion: 25
)

Realm.Configuration.defaultConfiguration = config
Expand Down
3 changes: 3 additions & 0 deletions Sources/Localization/fr.lproj/Localizable.strings
Expand Up @@ -25,6 +25,8 @@
"product-search.error-view.title" = "Une erreur s'est produite";
"product-search.error-view.subtitle" = "Veuillez r茅essayer";
"product-search.initial-view.slogan" = "l'information alimentaire ouverte";
"product-search.initial-view.offline.title-loading" = "Sauvegarde des produits hors-ligne : %d%%";
"product-search.initial-view.offline.subtitle" = "Plus de %d produits en scan instantan茅";

// Product Scanner
"product-scanner.overlay.user-help" = "Scannez le code-barres du produit";
Expand Down Expand Up @@ -163,6 +165,7 @@
"product-detail.ingredients.allergens-list" = "Substances ou produits provoquant des allergies ou intol茅rances";
"product-detail.ingredients.allergens-alert.title" = "Contient des allerg猫nes !";
"product-detail.ingredients.allergens-list.missing-infos" = "Ce produit n'est pas complet. En cons茅quence, nous n'avons pas pu 茅valuer la pr茅sence d'allerg猫nes.";
"product-detail.ingredients.allergens-list.offline-product" = "Ce produit provient de la base de donn茅e hors-ligne. En cons茅quence, nous n'avons pas pu 茅valuer la pr茅sence d'allerg猫nes.";
"product-detail.ingredients.traces-list" = "Traces 茅ventuelles";
"product-detail.ingredients.additives-list" = "Additifs";
"product-detail.ingredients.palm-oil-ingredients" = "Ingr茅dients issus de l'huile de palme";
Expand Down
13 changes: 13 additions & 0 deletions Sources/Models/DataManager.swift
Expand Up @@ -19,6 +19,8 @@ protocol DataManagerProtocol {
isSummary: Bool,
onSuccess: @escaping (Product?) -> Void, onError: @escaping (Error) -> Void)

func getOfflineProduct(forCode: String) -> RealmOfflineProduct?

// User
func logIn(username: String, password: String, onSuccess: @escaping () -> Void, onError: @escaping (Error) -> Void)

Expand All @@ -37,6 +39,9 @@ protocol DataManagerProtocol {
func addHistoryItem(_ product: Product)
func clearHistory()

// offline
func offlineProductStatus() -> RealmOfflineProductStatus?

// Settings
func addAllergy(toAllergen: Allergen)
func removeAllergy(toAllergen: Allergen)
Expand Down Expand Up @@ -94,6 +99,10 @@ class DataManager: DataManagerProtocol {
})
}

func getOfflineProduct(forCode: String) -> RealmOfflineProduct? {
return persistenceManager.getOfflineProduct(forCode: forCode)
}

// MARK: - User

func logIn(username: String, password: String, onSuccess: @escaping () -> Void, onError: @escaping (Error) -> Void) {
Expand Down Expand Up @@ -175,6 +184,10 @@ class DataManager: DataManagerProtocol {
persistenceManager.clearHistory()
}

func offlineProductStatus() -> RealmOfflineProductStatus? {
return persistenceManager.offlineProductStatus()
}

// MARK: - Product Add

func addProduct(_ product: Product, onSuccess: @escaping () -> Void, onError: @escaping (Error) -> Void) {
Expand Down
41 changes: 39 additions & 2 deletions Sources/Models/PersistenceManager.swift
Expand Up @@ -34,6 +34,12 @@ protocol PersistenceManagerProtocol {
func save(additives: [Additive])
func additive(forCode: String) -> Additive?

// Offline
func save(offlineProducts: [RealmOfflineProduct])
func getOfflineProduct(forCode: String) -> RealmOfflineProduct?
func updateOfflineProductStatus(percent: Double, savedProductsCount: Int)
func offlineProductStatus() -> RealmOfflineProductStatus?

// allergies settings
func addAllergy(toAllergen: Allergen)
func removeAllergy(toAllergen: Allergen)
Expand All @@ -52,14 +58,13 @@ class PersistenceManager: PersistenceManagerProtocol {

fileprivate func saveOrUpdate(objects: [Object]) {
let realm = getRealm()
print(Realm.Configuration.defaultConfiguration.fileURL!)

do {
try realm.write {
realm.add(objects, update: true)
}
} catch let error as NSError {
log.error(error)
log.error("ERROR SAVING INTO REALM \(error)")
Crashlytics.sharedInstance().recordError(error)
}
}
Expand Down Expand Up @@ -178,12 +183,23 @@ class PersistenceManager: PersistenceManagerProtocol {
return getRealm().object(ofType: Additive.self, forPrimaryKey: code)
}

func save(offlineProducts: [RealmOfflineProduct]) {
saveOrUpdate(objects: offlineProducts)
}

func getOfflineProduct(forCode: String) -> RealmOfflineProduct? {
return getRealm().object(ofType: RealmOfflineProduct.self, forPrimaryKey: forCode)
}

// MARK: User Preferences
fileprivate func getRealmUserPreferences() -> RealmUserPreferences {
if let prefs = getRealm().objects(RealmUserPreferences.self).first {
return prefs
}
let prefs = RealmUserPreferences()
if prefs.offlineStatus == nil {
prefs.offlineStatus = RealmOfflineProductStatus()
}
do {
let realm = getRealm()
try realm.write {
Expand Down Expand Up @@ -227,6 +243,27 @@ class PersistenceManager: PersistenceManagerProtocol {
}
}

func updateOfflineProductStatus(percent: Double, savedProductsCount: Int) {
let settings = getRealmUserPreferences()
let realm = getRealm()
do {
try realm.write {
if settings.offlineStatus == nil {
settings.offlineStatus = RealmOfflineProductStatus()
}
settings.offlineStatus?.percent = percent
settings.offlineStatus?.savedProductsCount = savedProductsCount
}
} catch let error as NSError {
log.error(error)
Crashlytics.sharedInstance().recordError(error)
}
}

func offlineProductStatus() -> RealmOfflineProductStatus? {
return getRealmUserPreferences().offlineStatus
}

func listAllergies() -> Results<Allergen> {
return getRealmUserPreferences().allergens.sorted(byKeyPath: "mainName", ascending: true)
}
Expand Down
24 changes: 24 additions & 0 deletions Sources/Models/Realm/RealmOfflineProduct.swift
@@ -0,0 +1,24 @@
//
// RealmOfflineProduct.swift
// OpenFoodFacts
//
// Created by Philippe Auriach on 05/03/2019.
// Copyright 漏 2019 Andr茅s Piz谩 B眉ckmann. All rights reserved.
//

import Foundation
import RealmSwift

class RealmOfflineProduct: Object {

@objc dynamic var barcode = ""
@objc dynamic var name: String?
@objc dynamic var quantity: String?
@objc dynamic var brands: String?
@objc dynamic var nutritionGrade: String?
@objc dynamic var novaGroup: String?

override static func primaryKey() -> String? {
return "barcode"
}
}
7 changes: 7 additions & 0 deletions Sources/Models/Realm/RealmUserPreferences.swift
Expand Up @@ -9,8 +9,15 @@
import Foundation
import RealmSwift

class RealmOfflineProductStatus: Object {
@objc dynamic var percent: Double = 0
@objc dynamic var savedProductsCount: Int = 0
}

class RealmUserPreferences: Object {

@objc dynamic var offlineStatus: RealmOfflineProductStatus? = RealmOfflineProductStatus()

let allergens = List<Allergen>()

}

0 comments on commit f9f4a75

Please sign in to comment.