mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
[AltStoreCore] Adds AppScreenshot to support dynamically-sized screenshots
Preserves StoreApp.imageURL field in database model for backwards compatibility.
This commit is contained in:
@@ -435,6 +435,7 @@
|
|||||||
D5F48B4C29CD0C48002B52A4 /* AsyncManaged.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F48B4929CD0B67002B52A4 /* AsyncManaged.swift */; };
|
D5F48B4C29CD0C48002B52A4 /* AsyncManaged.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F48B4929CD0B67002B52A4 /* AsyncManaged.swift */; };
|
||||||
D5F5AF2E28FDD2EC00C938F5 /* TestErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */; };
|
D5F5AF2E28FDD2EC00C938F5 /* TestErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */; };
|
||||||
D5F5AF7D28ECEA990067C736 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */; };
|
D5F5AF7D28ECEA990067C736 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */; };
|
||||||
|
D5F9821D2AB900060045751F /* AppScreenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F9821C2AB900060045751F /* AppScreenshot.swift */; };
|
||||||
D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */; };
|
D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */; };
|
||||||
D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; };
|
D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; };
|
||||||
D5FB28EC2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB28EB2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift */; };
|
D5FB28EC2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB28EB2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift */; };
|
||||||
@@ -1096,6 +1097,7 @@
|
|||||||
D5F48B4929CD0B67002B52A4 /* AsyncManaged.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncManaged.swift; sourceTree = "<group>"; };
|
D5F48B4929CD0B67002B52A4 /* AsyncManaged.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncManaged.swift; sourceTree = "<group>"; };
|
||||||
D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestErrors.swift; sourceTree = "<group>"; };
|
D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestErrors.swift; sourceTree = "<group>"; };
|
||||||
D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D5F9821C2AB900060045751F /* AppScreenshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshot.swift; sourceTree = "<group>"; };
|
||||||
D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = "<group>"; };
|
D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = "<group>"; };
|
||||||
D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = "<group>"; };
|
D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = "<group>"; };
|
||||||
D5FB28EB2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFontDescriptor+Bold.swift"; sourceTree = "<group>"; };
|
D5FB28EB2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFontDescriptor+Bold.swift"; sourceTree = "<group>"; };
|
||||||
@@ -1666,6 +1668,7 @@
|
|||||||
BF66EEC92501AECA007EE018 /* Account.swift */,
|
BF66EEC92501AECA007EE018 /* Account.swift */,
|
||||||
BF66EEC72501AECA007EE018 /* AppID.swift */,
|
BF66EEC72501AECA007EE018 /* AppID.swift */,
|
||||||
BF66EEC62501AECA007EE018 /* AppPermission.swift */,
|
BF66EEC62501AECA007EE018 /* AppPermission.swift */,
|
||||||
|
D5F9821C2AB900060045751F /* AppScreenshot.swift */,
|
||||||
D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */,
|
D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */,
|
||||||
BF66EECA2501AECA007EE018 /* DatabaseManager.swift */,
|
BF66EECA2501AECA007EE018 /* DatabaseManager.swift */,
|
||||||
D5FD4ECA2A9532960097BEE8 /* DatabaseManager+Async.swift */,
|
D5FD4ECA2A9532960097BEE8 /* DatabaseManager+Async.swift */,
|
||||||
@@ -3056,6 +3059,7 @@
|
|||||||
BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */,
|
BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */,
|
||||||
BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */,
|
BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */,
|
||||||
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */,
|
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */,
|
||||||
|
D5F9821D2AB900060045751F /* AppScreenshot.swift in Sources */,
|
||||||
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */,
|
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */,
|
||||||
BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */,
|
BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */,
|
||||||
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */,
|
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */,
|
||||||
|
|||||||
@@ -42,6 +42,21 @@
|
|||||||
</uniquenessConstraint>
|
</uniquenessConstraint>
|
||||||
</uniquenessConstraints>
|
</uniquenessConstraints>
|
||||||
</entity>
|
</entity>
|
||||||
|
<entity name="AppScreenshot" representedClassName="AppScreenshot" syncable="YES">
|
||||||
|
<attribute name="appBundleID" attributeType="String"/>
|
||||||
|
<attribute name="height" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="imageURL" attributeType="URI"/>
|
||||||
|
<attribute name="sourceID" attributeType="String"/>
|
||||||
|
<attribute name="width" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
|
||||||
|
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="screenshots" inverseEntity="StoreApp"/>
|
||||||
|
<uniquenessConstraints>
|
||||||
|
<uniquenessConstraint>
|
||||||
|
<constraint value="imageURL"/>
|
||||||
|
<constraint value="appBundleID"/>
|
||||||
|
<constraint value="sourceID"/>
|
||||||
|
</uniquenessConstraint>
|
||||||
|
</uniquenessConstraints>
|
||||||
|
</entity>
|
||||||
<entity name="AppVersion" representedClassName="AppVersion" syncable="YES">
|
<entity name="AppVersion" representedClassName="AppVersion" syncable="YES">
|
||||||
<attribute name="appBundleID" attributeType="String"/>
|
<attribute name="appBundleID" attributeType="String"/>
|
||||||
<attribute name="buildVersion" attributeType="String"/>
|
<attribute name="buildVersion" attributeType="String"/>
|
||||||
@@ -207,6 +222,7 @@
|
|||||||
<relationship name="loggedErrors" toMany="YES" deletionRule="Nullify" destinationEntity="LoggedError" inverseName="storeApp" inverseEntity="LoggedError"/>
|
<relationship name="loggedErrors" toMany="YES" deletionRule="Nullify" destinationEntity="LoggedError" inverseName="storeApp" inverseEntity="LoggedError"/>
|
||||||
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" destinationEntity="NewsItem" inverseName="storeApp" inverseEntity="NewsItem"/>
|
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" destinationEntity="NewsItem" inverseName="storeApp" inverseEntity="NewsItem"/>
|
||||||
<relationship name="permissions" toMany="YES" deletionRule="Cascade" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission"/>
|
<relationship name="permissions" toMany="YES" deletionRule="Cascade" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission"/>
|
||||||
|
<relationship name="screenshots" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppScreenshot" inverseName="app" inverseEntity="AppScreenshot"/>
|
||||||
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="apps" inverseEntity="Source"/>
|
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="apps" inverseEntity="Source"/>
|
||||||
<relationship name="versions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppVersion" inverseName="app" inverseEntity="AppVersion"/>
|
<relationship name="versions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppVersion" inverseName="app" inverseEntity="AppVersion"/>
|
||||||
<uniquenessConstraints>
|
<uniquenessConstraints>
|
||||||
|
|||||||
85
AltStoreCore/Model/AppScreenshot.swift
Normal file
85
AltStoreCore/Model/AppScreenshot.swift
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// AppScreenshot.swift
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 9/18/23.
|
||||||
|
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
@objc(AppScreenshot)
|
||||||
|
public class AppScreenshot: NSManagedObject, Fetchable, Decodable
|
||||||
|
{
|
||||||
|
/* Properties */
|
||||||
|
@NSManaged public private(set) var imageURL: URL
|
||||||
|
|
||||||
|
public private(set) var size: CGSize? {
|
||||||
|
get {
|
||||||
|
guard let width = self.width?.doubleValue, let height = self.height?.doubleValue else { return nil }
|
||||||
|
return CGSize(width: width, height: height)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if let newValue
|
||||||
|
{
|
||||||
|
self.width = NSNumber(value: newValue.width)
|
||||||
|
self.height = NSNumber(value: newValue.height)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.width = nil
|
||||||
|
self.height = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@NSManaged private var width: NSNumber?
|
||||||
|
@NSManaged private var height: NSNumber?
|
||||||
|
|
||||||
|
@NSManaged public internal(set) var appBundleID: String
|
||||||
|
@NSManaged public internal(set) var sourceID: String
|
||||||
|
|
||||||
|
/* Relationships */
|
||||||
|
@NSManaged public internal(set) var app: StoreApp?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey
|
||||||
|
{
|
||||||
|
case imageURL
|
||||||
|
case width
|
||||||
|
case height
|
||||||
|
}
|
||||||
|
|
||||||
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
|
{
|
||||||
|
super.init(entity: entity, insertInto: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal init(imageURL: URL, size: CGSize?, context: NSManagedObjectContext)
|
||||||
|
{
|
||||||
|
super.init(entity: AppScreenshot.entity(), insertInto: context)
|
||||||
|
|
||||||
|
self.imageURL = imageURL
|
||||||
|
self.size = size
|
||||||
|
}
|
||||||
|
|
||||||
|
public required init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||||
|
|
||||||
|
super.init(entity: AppScreenshot.entity(), insertInto: context)
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
self.imageURL = try container.decode(URL.self, forKey: .imageURL)
|
||||||
|
|
||||||
|
self.width = try container.decodeIfPresent(Int16.self, forKey: .width).map { NSNumber(value: $0) }
|
||||||
|
self.height = try container.decodeIfPresent(Int16.self, forKey: .height).map { NSNumber(value: $0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AppScreenshot
|
||||||
|
{
|
||||||
|
@nonobjc class func fetchRequest() -> NSFetchRequest<AppScreenshot>
|
||||||
|
{
|
||||||
|
return NSFetchRequest<AppScreenshot>(entityName: "AppScreenshot")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -156,6 +156,12 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
|||||||
{
|
{
|
||||||
appVersion.managedObjectContext?.delete(appVersion)
|
appVersion.managedObjectContext?.delete(appVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete previous screenshots (different than below).
|
||||||
|
for case let appScreenshot as AppScreenshot in previousApp._screenshots where appScreenshot.app == nil
|
||||||
|
{
|
||||||
|
appScreenshot.managedObjectContext?.delete(appScreenshot)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case is AppVersion where conflict.conflictingObjects.count == 2:
|
case is AppVersion where conflict.conflictingObjects.count == 2:
|
||||||
@@ -181,8 +187,9 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortedVersionIDsByGlobalAppID = [String: NSOrderedSet]()
|
|
||||||
var permissionsByGlobalAppID = [String: Set<AnyHashable>]()
|
var permissionsByGlobalAppID = [String: Set<AnyHashable>]()
|
||||||
|
var sortedVersionIDsByGlobalAppID = [String: NSOrderedSet]()
|
||||||
|
var sortedScreenshotURLsByGlobalAppID = [String: NSOrderedSet]()
|
||||||
|
|
||||||
var featuredAppIDsBySourceID = [String: [String]]()
|
var featuredAppIDsBySourceID = [String: [String]]()
|
||||||
|
|
||||||
@@ -212,10 +219,19 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
|||||||
databaseVersion.managedObjectContext?.delete(databaseVersion)
|
databaseVersion.managedObjectContext?.delete(databaseVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Screenshots
|
||||||
|
let contextScreenshotURLs = NSOrderedSet(array: contextApp._screenshots.lazy.compactMap { $0 as? AppScreenshot }.map { $0.imageURL })
|
||||||
|
for case let databaseScreenshot as AppScreenshot in databaseObject._screenshots where !contextScreenshotURLs.contains(databaseScreenshot.imageURL)
|
||||||
|
{
|
||||||
|
// Screenshot's imageURL does NOT exist in context, so delete existing databaseScreenshot.
|
||||||
|
databaseScreenshot.managedObjectContext?.delete(databaseScreenshot)
|
||||||
|
}
|
||||||
|
|
||||||
if let globallyUniqueID = contextApp.globallyUniqueID
|
if let globallyUniqueID = contextApp.globallyUniqueID
|
||||||
{
|
{
|
||||||
permissionsByGlobalAppID[globallyUniqueID] = contextPermissions
|
permissionsByGlobalAppID[globallyUniqueID] = contextPermissions
|
||||||
sortedVersionIDsByGlobalAppID[globallyUniqueID] = contextVersionIDs
|
sortedVersionIDsByGlobalAppID[globallyUniqueID] = contextVersionIDs
|
||||||
|
sortedScreenshotURLsByGlobalAppID[globallyUniqueID] = contextScreenshotURLs
|
||||||
}
|
}
|
||||||
|
|
||||||
case let databaseObject as Source:
|
case let databaseObject as Source:
|
||||||
@@ -295,6 +311,31 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
|||||||
|
|
||||||
appVersions = fixedAppVersions
|
appVersions = fixedAppVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Screenshots
|
||||||
|
if let sortedScreenshotURLs = sortedScreenshotURLsByGlobalAppID[globallyUniqueID],
|
||||||
|
let sortedScreenshotURLsArray = sortedScreenshotURLs.array as? [URL],
|
||||||
|
case let databaseScreenshotURLs = databaseObject.screenshots.map({ $0.imageURL }),
|
||||||
|
databaseScreenshotURLs != sortedScreenshotURLsArray
|
||||||
|
{
|
||||||
|
// Screenshot order is incorrect, so attempt to fix by re-sorting.
|
||||||
|
let fixedScreenshots = databaseObject.screenshots.sorted { (screenshotA, screenshotB) in
|
||||||
|
let indexA = sortedScreenshotURLs.index(of: screenshotA.imageURL)
|
||||||
|
let indexB = sortedScreenshotURLs.index(of: screenshotB.imageURL)
|
||||||
|
return indexA < indexB
|
||||||
|
}
|
||||||
|
|
||||||
|
let appScreenshotURLs = fixedScreenshots.map { $0.imageURL }
|
||||||
|
if appScreenshotURLs == sortedScreenshotURLsArray
|
||||||
|
{
|
||||||
|
databaseObject.setScreenshots(fixedScreenshots)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Screenshots are still not in correct order, but not worth throwing error so ignore.
|
||||||
|
print("Failed to re-sort screenshots into correct order. Expected:", sortedScreenshotURLsArray)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always update versions post-merging to make sure latestSupportedVersion is correct.
|
// Always update versions post-merging to make sure latestSupportedVersion is correct.
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
{
|
{
|
||||||
permission.sourceID = self.sourceIdentifier ?? ""
|
permission.sourceID = self.sourceIdentifier ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for screenshot in self.screenshots
|
||||||
|
{
|
||||||
|
screenshot.sourceID = self.sourceIdentifier ?? ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@NSManaged private var primitiveSourceIdentifier: String?
|
@NSManaged private var primitiveSourceIdentifier: String?
|
||||||
@@ -200,6 +205,10 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
guard let version = self.latestSupportedVersion else { return nil }
|
guard let version = self.latestSupportedVersion else { return nil }
|
||||||
return version.downloadURL
|
return version.downloadURL
|
||||||
}
|
}
|
||||||
|
@nonobjc public var screenshots: [AppScreenshot] {
|
||||||
|
return self._screenshots.array as! [AppScreenshot]
|
||||||
|
}
|
||||||
|
@NSManaged @objc(screenshots) private(set) var _screenshots: NSOrderedSet
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
@@ -212,19 +221,24 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
case bundleIdentifier
|
case bundleIdentifier
|
||||||
case developerName
|
case developerName
|
||||||
case localizedDescription
|
case localizedDescription
|
||||||
case version
|
|
||||||
case versionDescription
|
|
||||||
case versionDate
|
|
||||||
case iconURL
|
case iconURL
|
||||||
case screenshotURLs
|
case screenshotURLs
|
||||||
case downloadURL
|
case downloadURL
|
||||||
case platformURLs
|
case platformURLs
|
||||||
|
case screenshots
|
||||||
case tintColor
|
case tintColor
|
||||||
case subtitle
|
case subtitle
|
||||||
case permissions = "appPermissions"
|
case permissions = "appPermissions"
|
||||||
case size
|
case size
|
||||||
case isBeta = "beta"
|
case isBeta = "beta"
|
||||||
case versions
|
case versions
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
case version
|
||||||
|
case versionDescription
|
||||||
|
case versionDate
|
||||||
|
case downloadURL
|
||||||
|
case screenshotURLs
|
||||||
}
|
}
|
||||||
|
|
||||||
public required init(from decoder: Decoder) throws
|
public required init(from decoder: Decoder) throws
|
||||||
@@ -245,7 +259,6 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
self.subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle)
|
self.subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle)
|
||||||
|
|
||||||
self.iconURL = try container.decode(URL.self, forKey: .iconURL)
|
self.iconURL = try container.decode(URL.self, forKey: .iconURL)
|
||||||
self.screenshotURLs = try container.decodeIfPresent([URL].self, forKey: .screenshotURLs) ?? []
|
|
||||||
|
|
||||||
var downloadURL = try container.decodeIfPresent(URL.self, forKey: .downloadURL)
|
var downloadURL = try container.decodeIfPresent(URL.self, forKey: .downloadURL)
|
||||||
let platformURLs = try container.decodeIfPresent(PlatformURLs.self.self, forKey: .platformURLs)
|
let platformURLs = try container.decodeIfPresent(PlatformURLs.self.self, forKey: .platformURLs)
|
||||||
@@ -291,6 +304,33 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
|
|
||||||
self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false
|
self.isBeta = try container.decodeIfPresent(Bool.self, forKey: .isBeta) ?? false
|
||||||
|
|
||||||
|
if let screenshots = try container.decodeIfPresent([AppScreenshot].self, forKey: .screenshots)
|
||||||
|
{
|
||||||
|
for screenshot in screenshots
|
||||||
|
{
|
||||||
|
screenshot.appBundleID = self.bundleIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setScreenshots(screenshots)
|
||||||
|
}
|
||||||
|
else if let screenshotURLs = try container.decodeIfPresent([URL].self, forKey: .screenshotURLs)
|
||||||
|
{
|
||||||
|
// Assume 9:16 iPhone 8 screen dimensions for legacy screenshotURLs.
|
||||||
|
let legacyAspectRatio = CGSize(width: 750, height: 1334)
|
||||||
|
|
||||||
|
let screenshots = screenshotURLs.map { imageURL in
|
||||||
|
let screenshot = AppScreenshot(imageURL: imageURL, size: legacyAspectRatio, context: context)
|
||||||
|
screenshot.appBundleID = self.bundleIdentifier
|
||||||
|
return screenshot
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setScreenshots(screenshots)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.setScreenshots([])
|
||||||
|
}
|
||||||
|
|
||||||
if let appPermissions = try container.decodeIfPresent(AppPermissions.self, forKey: .permissions)
|
if let appPermissions = try container.decodeIfPresent(AppPermissions.self, forKey: .permissions)
|
||||||
{
|
{
|
||||||
let allPermissions = appPermissions.entitlements + appPermissions.privacy
|
let allPermissions = appPermissions.entitlements + appPermissions.privacy
|
||||||
@@ -402,6 +442,26 @@ internal extension StoreApp
|
|||||||
|
|
||||||
self._permissions = permissions as NSSet
|
self._permissions = permissions as NSSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setScreenshots(_ screenshots: [AppScreenshot])
|
||||||
|
{
|
||||||
|
for case let screenshot as AppScreenshot in self._screenshots
|
||||||
|
{
|
||||||
|
if screenshots.contains(screenshot)
|
||||||
|
{
|
||||||
|
screenshot.app = self
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
screenshot.app = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self._screenshots = NSOrderedSet(array: screenshots)
|
||||||
|
|
||||||
|
// Backwards compatibility
|
||||||
|
self.screenshotURLs = screenshots.map { $0.imageURL }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension StoreApp
|
public extension StoreApp
|
||||||
|
|||||||
Reference in New Issue
Block a user