From bdb1d68b6bffc2fe7a868c8196451ff42e3892af Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 4 Apr 2023 13:46:04 -0500 Subject: [PATCH] =?UTF-8?q?[AltStoreCore]=20Supports=20additional=20source?= =?UTF-8?q?=20JSON=20values=20for=20detailed=20=E2=80=9CAbout=E2=80=9D=20p?= =?UTF-8?q?age?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AltStore 11.xcdatamodel/contents | 11 ++- AltStoreCore/Model/Source.swift | 93 ++++++++++++++++++- AltStoreCore/Model/StoreApp.swift | 2 + 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 11.xcdatamodel/contents b/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 11.xcdatamodel/contents index 9e5dc407..61dba015 100644 --- a/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 11.xcdatamodel/contents +++ b/AltStoreCore/Model/AltStore.xcdatamodeld/AltStore 11.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -150,10 +150,18 @@ + + + + + + + + @@ -178,6 +186,7 @@ + diff --git a/AltStoreCore/Model/Source.swift b/AltStoreCore/Model/Source.swift index 8f1a72a7..5b60eeba 100644 --- a/AltStoreCore/Model/Source.swift +++ b/AltStoreCore/Model/Source.swift @@ -176,6 +176,27 @@ public struct SourceJSON: Codable { } +public extension Source +{ + // Fallbacks for optional JSON values. + + var effectiveIconURL: URL? { + return self.iconURL ?? self.apps.first?.iconURL + } + + var effectiveHeaderImageURL: URL? { + return self.headerImageURL ?? self.effectiveIconURL + } + + var effectiveTintColor: UIColor? { + return self.tintColor ?? self.apps.first?.tintColor + } + + var effectiveFeaturedApps: [StoreApp] { + return self.featuredApps ?? self.apps + } +} + @objc(Source) public class Source: NSManagedObject, Fetchable, Decodable { @@ -184,6 +205,17 @@ public class Source: NSManagedObject, Fetchable, Decodable @NSManaged public var identifier: String @NSManaged public var sourceURL: URL + /* Source Detail */ + @NSManaged public var subtitle: String? + @NSManaged public var websiteURL: URL? + @NSManaged public var localizedDescription: String? + + // Optional properties with fallbacks. + // `private` to prevent accidentally using instead of `effective[PropertyName]` + @NSManaged private var iconURL: URL? + @NSManaged private var headerImageURL: URL? + @NSManaged private var tintColor: UIColor? + @NSManaged public var error: NSError? /* Non-Core Data Properties */ @@ -193,6 +225,9 @@ public class Source: NSManagedObject, Fetchable, Decodable @objc(apps) @NSManaged public private(set) var _apps: NSOrderedSet @objc(newsItems) @NSManaged public private(set) var _newsItems: NSOrderedSet + @objc(featuredApps) @NSManaged public private(set) var _featuredApps: NSOrderedSet + @objc(hasFeaturedApps) @NSManaged private var _hasFeaturedApps: Bool + @nonobjc public var apps: [StoreApp] { get { return self._apps.array as! [StoreApp] @@ -211,14 +246,27 @@ public class Source: NSManagedObject, Fetchable, Decodable } } + // `internal` to prevent accidentally using instead of `effectiveFeaturedApps` + @nonobjc internal var featuredApps: [StoreApp]? { + return self._hasFeaturedApps ? self._featuredApps.array as? [StoreApp] : nil + } + private enum CodingKeys: String, CodingKey { case name case identifier case sourceURL - case userInfo + case subtitle + case localizedDescription = "description" + case iconURL + case headerImageURL = "headerURL" + case websiteURL = "website" + case tintColor + case apps case news + case featuredApps + case userInfo } private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) @@ -241,6 +289,22 @@ public class Source: NSManagedObject, Fetchable, Decodable self.name = try container.decode(String.self, forKey: .name) self.identifier = try container.decode(String.self, forKey: .identifier) + // Optional Values + self.subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle) + self.websiteURL = try container.decodeIfPresent(URL.self, forKey: .websiteURL) + self.localizedDescription = try container.decodeIfPresent(String.self, forKey: .localizedDescription) + self.iconURL = try container.decodeIfPresent(URL.self, forKey: .iconURL) + self.headerImageURL = try container.decodeIfPresent(URL.self, forKey: .headerImageURL) + + if let tintColorHex = try container.decodeIfPresent(String.self, forKey: .tintColor) + { + guard let tintColor = UIColor(hexString: tintColorHex) else { + throw DecodingError.dataCorruptedError(forKey: .tintColor, in: container, debugDescription: "Hex code is invalid.") + } + + self.tintColor = tintColor + } + let userInfo = try container.decodeIfPresent([String: String].self, forKey: .userInfo) self.userInfo = userInfo?.reduce(into: [:]) { $0[ALTSourceUserInfoKey($1.key)] = $1.value } @@ -275,6 +339,10 @@ public class Source: NSManagedObject, Fetchable, Decodable } } self._newsItems = NSMutableOrderedSet(array: newsItems) + + let featuredAppBundleIDs = try container.decodeIfPresent([String].self, forKey: .featuredApps) + let featuredApps = featuredAppBundleIDs?.compactMap { appsByID[$0] } + self.setFeaturedApps(featuredApps) } catch { @@ -288,6 +356,29 @@ public class Source: NSManagedObject, Fetchable, Decodable } } +internal extension Source +{ + func setFeaturedApps(_ featuredApps: [StoreApp]?) + { + // Explicitly update relationships for all apps to ensure featuredApps merges correctly. + + for case let storeApp as StoreApp in self._apps + { + if let featuredApps, featuredApps.contains(where: { $0.bundleIdentifier == storeApp.bundleIdentifier }) + { + storeApp.featuringSource = self + } + else + { + storeApp.featuringSource = nil + } + } + + self._featuredApps = NSOrderedSet(array: featuredApps ?? []) + self._hasFeaturedApps = (featuredApps != nil) + } +} + public extension Source { @nonobjc class func fetchRequest() -> NSFetchRequest diff --git a/AltStoreCore/Model/StoreApp.swift b/AltStoreCore/Model/StoreApp.swift index 0116a338..897de09e 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -144,6 +144,8 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable @NSManaged public var newsItems: Set @NSManaged @objc(source) public var _source: Source? + @NSManaged public internal(set) var featuringSource: Source? + @NSManaged @objc(permissions) public var _permissions: NSOrderedSet @NSManaged @objc(latestVersion) public private(set) var latestSupportedVersion: AppVersion?