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

Commit

Permalink
Add Labels to TaxonomiesService and Product view
Browse files Browse the repository at this point in the history
Labels were partially implemented but still needed to be incorporated
into the API Models and Network Services. In doing so, it was discovered
that the Taxonomies weren't being downloaded to Realm on first launch of
the app as intended. After fixing that and adding the Label taxonomy to
Models, they were successfully retrieved and stored. Then, to allow
Labels to be displayed by ProductDetailViewController, they were
included in the OFFJson mapping. The downloaded taxonomies were then
used to translate Labels and code was added to fix translation of
Categories. Lastly, the functionality to link link Labels to their OFF
urls was added.

Closes #719
  • Loading branch information
jncosideout authored and teolemon committed Oct 6, 2020
1 parent 699fe67 commit 685fc79
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 6 deletions.
4 changes: 4 additions & 0 deletions Sources/Helpers/OFFUrlsHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ class OFFUrlsHelper: NSObject {
return URL(string: "\(baseUrl())/nucleotide/\(nucleotide.code)")!
}

static func url(forLabel label: Label) -> URL {
return URL(string: "\(baseUrl())/label/\(label.code)")!
}

// Not sure if there is a taxonomy for this
/*
static func url(forOther other: OtherNutritionalSubstance) -> URL {
Expand Down
4 changes: 3 additions & 1 deletion Sources/Models/API/OFFReadAPIkeysJSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ struct OFFJson {
//static let IngredientsIdsDebugKey = "ingredients_ids_debug"
//static let IngredientsThatMayBeFromPalmOilNKey = "ingredients_that_may_be_from_palm_oil_n"
static let LabelsKey = "labels"
static let LabelsTagsKey = "labels_tags"
//static let LabelsPrevHierarchyKey = "labels_prev_hierarchy"
//static let LcKey = "lc"
static let MineralsTagsKey = "minerals_tags"
Expand Down Expand Up @@ -375,7 +376,8 @@ struct OFFJson {
OFFJson.IngredientsTextKey,
OFFJson.IngredientsThatMayBeFromPalmOilTagsKey,
// OFFJson.LabelsHierarchyKey,
// OFFJson.LabelsTagsKey,
OFFJson.LabelsKey,
OFFJson.LabelsTagsKey,
OFFJson.LangKey,
OFFJson.ProductNameLanguagesKey,
OFFJson.GenericNameLanguagesKey,
Expand Down
3 changes: 2 additions & 1 deletion Sources/Models/API/Product.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ struct Product: Mappable {
var manufacturingPlaces: String?
var origins: String?
var labels: [String]?
var labelsTags: [String]?
var citiesTags: [String]?
var embCodesTags: [String]?
var stores: [String]?
Expand Down Expand Up @@ -303,6 +304,7 @@ struct Product: Mappable {
manufacturingPlaces <- map[OFFJson.ManufacturingPlacesKey]
origins <- map[OFFJson.OriginsKey]
labels <- (map[OFFJson.LabelsKey], ArrayTransform())
labelsTags <- map[OFFJson.LabelsTagsKey]
citiesTags <- map[OFFJson.CitiesTagsKey]
// countries <- (map[OFFJson.CountriesKey], ArrayTransform())
countriesTags <- map[OFFJson.CountriesTagsKey]
Expand All @@ -316,7 +318,6 @@ struct Product: Mappable {
imageUrl <- map[OFFJson.ImageUrlKey]
ingredientsImageUrlDecoded <- map[OFFJson.ImageIngredientsUrlKey]
ingredientsListDecoded <- map[OFFJson.IngredientsKey]
labels <- (map[OFFJson.LabelsKey], ArrayTransform())
lang <- map[OFFJson.LangKey]
languageCodes <- map[OFFJson.LanguageCodesKey]
manufacturingPlaces <- map[OFFJson.ManufacturingPlacesKey]
Expand Down
49 changes: 49 additions & 0 deletions Sources/Models/API/Taxonomies/Label.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Label.swift
// OpenFoodFacts
//
// Created by Alexander Scott Beaty on 8/23/20.
//

import Foundation
import RealmSwift
import ObjectMapper

class Label: Object {

@objc dynamic var code = ""

let parents = List<String>()
let children = List<String>()
let names = List<Tag>()

@objc dynamic var mainName = "" // name in the language of the app, for sorting
@objc dynamic var indexedNames = "" // all names concatenated, for search

convenience init(code: String, parents: [String], children: [String], names: [Tag]) {
self.init()
self.code = code

self.parents.removeAll()
self.parents.append(objectsIn: parents)

self.children.removeAll()
self.children.append(objectsIn: children)

self.names.removeAll()
self.names.append(objectsIn: names)

self.mainName = names.chooseForCurrentLanguage()?.value ?? ""
self.indexedNames = names.map({ (tag) -> String in
return tag.languageCode.appending(":").appending(tag.value)
}).joined(separator: " ||| ") // group all names to be able to query on only one field, independently of language
}

override static func primaryKey() -> String? {
return "code"
}

override static func indexedProperties() -> [String] {
return ["mainName", "indexedNames"]
}
}
6 changes: 6 additions & 0 deletions Sources/Models/DataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ protocol DataManagerProtocol {
func ingredientsAnalysis(forProduct product: Product) -> [IngredientsAnalysisDetail]
func ingredientsAnalysis(forTag tag: String) -> IngredientsAnalysis?
func ingredientsAnalysisConfig(forTag tag: String) -> IngredientsAnalysisConfig?
func label(forTag: String) -> Label?

func getTagline(_ callback: @escaping (_: Tagline?) -> Void)

Expand Down Expand Up @@ -257,6 +258,11 @@ class DataManager: DataManagerProtocol {
taxonomiesApi.getTagline(callback)
}

func label(forTag tag: String) -> Label? {
let myLabel = persistenceManager.label(forCode: tag)
return myLabel
}

// MARK: - Settings
func addAllergy(toAllergen: Allergen) {
persistenceManager.addAllergy(toAllergen: toAllergen)
Expand Down
18 changes: 18 additions & 0 deletions Sources/Models/PersistenceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ protocol PersistenceManagerProtocol {
func tagLine() -> Tagline?
var additivesIsEmpty: Bool { get }

func save(labels: [Label])
func label(forCode: String) -> Label?
var labelsIsEmpty: Bool { get }

// Offline
func save(offlineProducts: [RealmOfflineProduct])
func getOfflineProduct(forCode: String) -> RealmOfflineProduct?
Expand Down Expand Up @@ -365,6 +369,20 @@ class PersistenceManager: PersistenceManagerProtocol {
return getRealm().object(ofType: IngredientsAnalysisConfig.self, forPrimaryKey: code)
}

func save(labels: [Label]) {
saveOrUpdate(objects: labels)
log.info("Saved \(labels.count) labels in taxonomy database")
}

func label(forCode code: String) -> Label? {
return getRealm().object(ofType: Label.self, forPrimaryKey: code)
}

var labelsIsEmpty: Bool {
getRealm().objects(Label.self).isEmpty
}

// Offline Products
func save(offlineProducts: [RealmOfflineProduct]) {
saveOrUpdate(objects: offlineProducts)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Models/Transforms/TagTransform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class Tag: Object {

/// choose the most appropriate tags based on the language passed in parameters, default to english if not found
static func choose(inTags tags: [Tag], forLanguageCode languageCode: String? = nil, defaultToFirst: Bool = false) -> Tag? {
let lang = languageCode ?? Bundle.main.preferredLocalizations.first ?? "en"
let lang = languageCode ?? Bundle.main.currentLocalization

if let tag = tags.first(where: { (tag: Tag) -> Bool in
return tag.languageCode == lang
Expand Down
13 changes: 13 additions & 0 deletions Sources/Network/TaxonomiesParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ struct TaxonomiesParser: TaxonomiesParserProtocol {
return ingredientsAnalysisConfig
}

func parseLabels(data: [String: Any]) -> [Label] {
let labels = data.compactMap({ (labelCode: String, value: Any) -> Label? in
let tags = parseTags(value: value)
let parents = parseParents(value: value)
let children = parseChildren(value: value)
return Label(code: labelCode,
parents: parents,
children: children,
names: tags)
})
return labels
}

// MARK: - Private Helper Methods

private func parseTags(value: Any) -> [Tag] {
Expand Down
2 changes: 2 additions & 0 deletions Sources/Network/TaxonomiesRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct TaxonomiesRequest: URLRequestConvertible {
return route.rawValue + "/data/" + Endpoint.get
case (.post, _):
return Endpoint.post + route.rawValue
case (.get, _):
return Endpoint.get + "/data/" + route.rawValue
default:
return ""
}
Expand Down
32 changes: 30 additions & 2 deletions Sources/Network/TaxonomiesService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum TaxonomiesRoute: String {
case getMinerals = "taxonomies/minerals.json"
case getNucleotides = "taxonomies/nucleotides.json"
case getInvalidBarcodes = "invalid-barcodes.json"
case getLabels = "taxonomies/labels.json"
}

enum FilesRouter: URLRequestConvertible {
Expand Down Expand Up @@ -357,6 +358,27 @@ class TaxonomiesService: TaxonomiesApi {
}
}

fileprivate func refreshLabels(_ callback: @escaping (_: Bool) -> Void) {
do {
let request = try TaxonomiesRequest(route: .getLabels, requestType: .get).asURLRequest()
Alamofire.request(request)
.responseJSON { (response) in
switch response.result {
case .success(let responseBody):
if let json = responseBody as? [String: Any] {
let labels = self.taxonomiesParser.parseLabels(data: json)
self.persistenceManager.save(labels: labels)
callback(true)
}
case .failure(let error):
AnalyticsManager.record(error: error)
callback(false)
}
}
} catch {
callback(false)
}
}
// swiftlint:disable identifier_name

/// increment last number each time you want to force a refresh. Useful if you add a new refresh method or a new field
Expand All @@ -380,7 +402,8 @@ class TaxonomiesService: TaxonomiesApi {
persistenceManager.nucleotidesIsEmpty ||
// persistenceManager.otherNutritionalSubstancesIsEmpty ||
persistenceManager.ingredientsAnalysisIsEmpty ||
persistenceManager.ingredientsAnalysisConfigIsEmpty
persistenceManager.ingredientsAnalysisConfigIsEmpty ||
persistenceManager.labelsIsEmpty
if shouldDownload {
downloadTaxonomies()
} else {
Expand Down Expand Up @@ -454,12 +477,17 @@ class TaxonomiesService: TaxonomiesApi {
})

group.enter()

self.refreshInvalidBarcodes { (success) in
allSuccess = allSuccess && success
group.leave()
}

group.enter()
self.refreshLabels({ (success) in
allSuccess = allSuccess && success
group.leave()
})

group.wait()

if allSuccess {
Expand Down
1 change: 1 addition & 0 deletions Sources/Protocols/TaxonomiesParserProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ protocol TaxonomiesParserProtocol {
func parseAdditives(data: [String: Any]) -> [Additive]
func parseIngredientsAnalysis(data: [String: Any]) -> [IngredientsAnalysis]
func parseIngredientsAnalysisConfig(data: [String: Any]) -> [IngredientsAnalysisConfig]
func parseLabels(data: [String: Any]) -> [Label]
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,15 @@ class ProductDetailViewController: ButtonBarPagerTabStripViewController, DataMan
return NSAttributedString(string: categoryTag)
}), label: InfoRowKey.categories.localizedString)

createFormRow(with: &rows, item: product.labels, label: InfoRowKey.labels.localizedString)
createFormRow(with: &rows, item: product.labelsTags?.map({ (labelTag: String) -> NSAttributedString in
if let label = dataManager.label(forTag: labelTag) {
if let name = Tag.choose(inTags: Array(label.names)) {
return NSAttributedString(string: name.value, attributes: [NSAttributedString.Key.link : OFFUrlsHelper.url(forLabel: label)])
}
}
return NSAttributedString(string: labelTag)
}), label: InfoRowKey.labels.localizedString)

createFormRow(with: &rows, item: product.citiesTags, label: InfoRowKey.citiesTags.localizedString)

createFormRow(with: &rows, item: product.embCodesTags?.map({ (tag: String) -> NSAttributedString in
Expand Down
7 changes: 7 additions & 0 deletions Tests/Models/PersistenceManagerMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,13 @@ class PersistenceManagerMock: PersistenceManagerProtocol {
return nil
}

func save(labels: [Label]) {
}

func label(forCode: String) -> Label? {
return nil
}

func save(offlineProducts: [RealmOfflineProduct]) {
}

Expand Down

0 comments on commit 685fc79

Please sign in to comment.