mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-10 23:33:29 +01:00
Adds News tab
This commit is contained in:
@@ -32,6 +32,25 @@
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NewsItem" representedClassName="NewsItem" syncable="YES">
|
||||
<attribute name="appID" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="caption" attributeType="String" syncable="YES"/>
|
||||
<attribute name="date" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="externalURL" optional="YES" attributeType="URI" syncable="YES"/>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="imageURL" optional="YES" attributeType="URI" syncable="YES"/>
|
||||
<attribute name="isSilent" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="tintColor" optional="YES" attributeType="Transformable" syncable="YES"/>
|
||||
<attribute name="title" attributeType="String" syncable="YES"/>
|
||||
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="newsItems" inverseEntity="Source" syncable="YES"/>
|
||||
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="newsItems" inverseEntity="StoreApp" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PatreonAccount" representedClassName="PatreonAccount" syncable="YES">
|
||||
<attribute name="firstName" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
@@ -59,6 +78,7 @@
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="sourceURL" attributeType="URI" syncable="YES"/>
|
||||
<relationship name="apps" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="StoreApp" inverseName="source" inverseEntity="StoreApp" syncable="YES"/>
|
||||
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="NewsItem" inverseName="source" inverseEntity="NewsItem" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
@@ -82,6 +102,7 @@
|
||||
<attribute name="versionDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="versionDescription" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="storeApp" inverseEntity="InstalledApp" syncable="YES"/>
|
||||
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" destinationEntity="NewsItem" inverseName="storeApp" inverseEntity="NewsItem" syncable="YES"/>
|
||||
<relationship name="permissions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission" syncable="YES"/>
|
||||
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="apps" inverseEntity="Source" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
@@ -106,10 +127,11 @@
|
||||
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
|
||||
<element name="AppPermission" positionX="-45" positionY="90" width="128" height="90"/>
|
||||
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="150"/>
|
||||
<element name="NewsItem" positionX="-45" positionY="126" width="128" height="225"/>
|
||||
<element name="PatreonAccount" positionX="-45" positionY="117" width="128" height="105"/>
|
||||
<element name="RefreshAttempt" positionX="-45" positionY="117" width="128" height="105"/>
|
||||
<element name="Source" positionX="-45" positionY="99" width="128" height="105"/>
|
||||
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="315"/>
|
||||
<element name="Source" positionX="-45" positionY="99" width="128" height="120"/>
|
||||
<element name="StoreApp" positionX="-63" positionY="-18" width="128" height="330"/>
|
||||
<element name="Team" positionX="-45" positionY="81" width="128" height="120"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -34,6 +34,7 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
||||
guard let conflictedObject = conflict.conflictingObjects.first as? Source else { break }
|
||||
|
||||
let bundleIdentifiers = Set(conflictedObject.apps.map { $0.bundleIdentifier })
|
||||
let newsItemIdentifiers = Set(conflictedObject.newsItems.map { $0.identifier })
|
||||
|
||||
for app in databaseObject.apps
|
||||
{
|
||||
@@ -44,6 +45,15 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
||||
}
|
||||
}
|
||||
|
||||
for newsItem in databaseObject.newsItems
|
||||
{
|
||||
if !newsItemIdentifiers.contains(newsItem.identifier)
|
||||
{
|
||||
// No longer listed in Source, so remove it from database.
|
||||
newsItem.managedObjectContext?.delete(newsItem)
|
||||
}
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
90
AltStore/Model/NewsItem.swift
Normal file
90
AltStore/Model/NewsItem.swift
Normal file
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// NewsItem.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 8/29/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
@objc(NewsItem)
|
||||
class NewsItem: NSManagedObject, Decodable, Fetchable
|
||||
{
|
||||
/* Properties */
|
||||
@NSManaged var identifier: String
|
||||
@NSManaged var date: Date
|
||||
|
||||
@NSManaged var title: String
|
||||
@NSManaged var caption: String
|
||||
@NSManaged var tintColor: UIColor
|
||||
@NSManaged var sortIndex: Int32
|
||||
@NSManaged var isSilent: Bool
|
||||
|
||||
@NSManaged var imageURL: URL?
|
||||
@NSManaged var externalURL: URL?
|
||||
|
||||
@NSManaged var appID: String?
|
||||
|
||||
/* Relationships */
|
||||
@NSManaged var storeApp: StoreApp?
|
||||
@NSManaged var source: Source?
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case identifier
|
||||
case date
|
||||
case title
|
||||
case caption
|
||||
case tintColor
|
||||
case imageURL
|
||||
case externalURL = "url"
|
||||
case appID
|
||||
case notify
|
||||
}
|
||||
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||
{
|
||||
super.init(entity: entity, insertInto: context)
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws
|
||||
{
|
||||
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||
|
||||
super.init(entity: NewsItem.entity(), insertInto: context)
|
||||
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.identifier = try container.decode(String.self, forKey: .identifier)
|
||||
self.date = try container.decode(Date.self, forKey: .date)
|
||||
|
||||
self.title = try container.decode(String.self, forKey: .title)
|
||||
self.caption = try container.decode(String.self, forKey: .caption)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
self.imageURL = try container.decodeIfPresent(URL.self, forKey: .imageURL)
|
||||
self.externalURL = try container.decodeIfPresent(URL.self, forKey: .externalURL)
|
||||
|
||||
self.appID = try container.decodeIfPresent(String.self, forKey: .appID)
|
||||
|
||||
let notify = try container.decodeIfPresent(Bool.self, forKey: .notify) ?? false
|
||||
self.isSilent = !notify
|
||||
}
|
||||
}
|
||||
|
||||
extension NewsItem
|
||||
{
|
||||
@nonobjc class func fetchRequest() -> NSFetchRequest<NewsItem>
|
||||
{
|
||||
return NSFetchRequest<NewsItem>(entityName: "NewsItem")
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ class Source: NSManagedObject, Fetchable, Decodable
|
||||
|
||||
/* Relationships */
|
||||
@objc(apps) @NSManaged private(set) var _apps: NSOrderedSet
|
||||
@objc(newsItems) @NSManaged private(set) var _newsItems: NSOrderedSet
|
||||
|
||||
@nonobjc var apps: [StoreApp] {
|
||||
get {
|
||||
@@ -33,12 +34,22 @@ class Source: NSManagedObject, Fetchable, Decodable
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc var newsItems: [NewsItem] {
|
||||
get {
|
||||
return self._newsItems.array as! [NewsItem]
|
||||
}
|
||||
set {
|
||||
self._newsItems = NSOrderedSet(array: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case name
|
||||
case identifier
|
||||
case sourceURL
|
||||
case apps
|
||||
case news
|
||||
}
|
||||
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||
@@ -63,10 +74,31 @@ class Source: NSManagedObject, Fetchable, Decodable
|
||||
app.sortIndex = Int32(index)
|
||||
}
|
||||
|
||||
let newsItems = try container.decodeIfPresent([NewsItem].self, forKey: .news) ?? []
|
||||
for (index, item) in newsItems.enumerated()
|
||||
{
|
||||
item.sortIndex = Int32(index)
|
||||
}
|
||||
|
||||
context.insert(self)
|
||||
|
||||
let appsByID = Dictionary(apps.map { ($0.bundleIdentifier, $0) }, uniquingKeysWith: { (a, b) in return a })
|
||||
|
||||
for newsItem in newsItems
|
||||
{
|
||||
newsItem.source = self
|
||||
|
||||
guard let appID = newsItem.appID else { continue }
|
||||
|
||||
if let storeApp = appsByID[appID]
|
||||
{
|
||||
newsItem.storeApp = storeApp
|
||||
}
|
||||
}
|
||||
|
||||
// Must assign after we're inserted into context.
|
||||
self._apps = NSMutableOrderedSet(array: apps)
|
||||
self._newsItems = NSMutableOrderedSet(array: newsItems)
|
||||
|
||||
print("Downloaded Order:", self.apps.map { $0.bundleIdentifier })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user