mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
[AltStore] Adds redesigned AppViewController to view/download AltStore apps
This commit is contained in:
@@ -27,12 +27,18 @@
|
||||
<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="app" inverseEntity="InstalledApp" syncable="YES"/>
|
||||
<relationship name="permissions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="AppPermission" representedClassName="AppPermission" syncable="YES">
|
||||
<attribute name="type" attributeType="String" syncable="YES"/>
|
||||
<attribute name="usageDescription" attributeType="String" syncable="YES"/>
|
||||
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="App" inverseName="permissions" inverseEntity="App" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="InstalledApp" representedClassName="InstalledApp" syncable="YES">
|
||||
<attribute name="bundleIdentifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
@@ -59,8 +65,9 @@
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Account" positionX="-36" positionY="90" width="128" height="135"/>
|
||||
<element name="App" positionX="-63" positionY="-18" width="128" height="240"/>
|
||||
<element name="App" positionX="-63" positionY="-18" width="128" height="255"/>
|
||||
<element name="InstalledApp" positionX="-63" positionY="0" width="128" height="120"/>
|
||||
<element name="Team" positionX="-45" positionY="81" width="128" height="120"/>
|
||||
<element name="AppPermission" positionX="-45" positionY="90" width="128" height="90"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -39,6 +39,11 @@ class App: NSManagedObject, Decodable, Fetchable
|
||||
|
||||
/* Relationships */
|
||||
@NSManaged private(set) var installedApp: InstalledApp?
|
||||
@objc(permissions) @NSManaged var _permissions: NSOrderedSet
|
||||
|
||||
@nonobjc var permissions: [AppPermission] {
|
||||
return self._permissions.array as! [AppPermission]
|
||||
}
|
||||
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||
{
|
||||
@@ -59,6 +64,7 @@ class App: NSManagedObject, Decodable, Fetchable
|
||||
case downloadURL
|
||||
case tintColor
|
||||
case subtitle
|
||||
case permissions
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws
|
||||
@@ -93,7 +99,12 @@ class App: NSManagedObject, Decodable, Fetchable
|
||||
self.tintColor = tintColor
|
||||
}
|
||||
|
||||
let permissions = try container.decodeIfPresent([AppPermission].self, forKey: .permissions) ?? []
|
||||
|
||||
context.insert(self)
|
||||
|
||||
// Must assign after we're inserted into context.
|
||||
self._permissions = NSOrderedSet(array: permissions)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
88
AltStore/Model/AppPermission.swift
Normal file
88
AltStore/Model/AppPermission.swift
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// AppPermission.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 7/23/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import UIKit
|
||||
|
||||
extension ALTAppPermissionType
|
||||
{
|
||||
var localizedShortName: String? {
|
||||
switch self
|
||||
{
|
||||
case .photos: return NSLocalizedString("Photos", comment: "")
|
||||
case .backgroundAudio: return NSLocalizedString("Audio (BG)", comment: "")
|
||||
case .backgroundFetch: return NSLocalizedString("Fetch (BG)", comment: "")
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var localizedName: String? {
|
||||
switch self
|
||||
{
|
||||
case .photos: return NSLocalizedString("Photos", comment: "")
|
||||
case .backgroundAudio: return NSLocalizedString("Background Audio", comment: "")
|
||||
case .backgroundFetch: return NSLocalizedString("Background Fetch", comment: "")
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
var icon: UIImage? {
|
||||
switch self
|
||||
{
|
||||
case .photos: return UIImage(named: "PhotosPermission")
|
||||
case .backgroundAudio: return UIImage(named: "BackgroundAudioPermission")
|
||||
case .backgroundFetch: return UIImage(named: "BackgroundFetchPermission")
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc(AppPermission)
|
||||
class AppPermission: NSManagedObject, Decodable, Fetchable
|
||||
{
|
||||
/* Properties */
|
||||
@NSManaged var type: ALTAppPermissionType
|
||||
@NSManaged var usageDescription: String
|
||||
|
||||
/* Relationships */
|
||||
@NSManaged private(set) var app: App!
|
||||
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||
{
|
||||
super.init(entity: entity, insertInto: context)
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case type
|
||||
case usageDescription
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws
|
||||
{
|
||||
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
|
||||
|
||||
super.init(entity: AppPermission.entity(), insertInto: nil)
|
||||
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.usageDescription = try container.decode(String.self, forKey: .usageDescription)
|
||||
|
||||
let rawType = try container.decode(String.self, forKey: .type)
|
||||
self.type = ALTAppPermissionType(rawValue: rawType)
|
||||
|
||||
context.insert(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension AppPermission
|
||||
{
|
||||
@nonobjc class func fetchRequest() -> NSFetchRequest<AppPermission>
|
||||
{
|
||||
return NSFetchRequest<AppPermission>(entityName: "AppPermission")
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public class DatabaseManager
|
||||
private init()
|
||||
{
|
||||
self.persistentContainer = RSTPersistentContainer(name: "AltStore")
|
||||
self.persistentContainer.preferredMergePolicy = MergePolicy()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
39
AltStore/Model/MergePolicy.swift
Normal file
39
AltStore/Model/MergePolicy.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// MergePolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 7/23/19.
|
||||
// Copyright © 2019 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
import Roxas
|
||||
|
||||
open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
||||
{
|
||||
open override func resolve(constraintConflicts conflicts: [NSConstraintConflict]) throws
|
||||
{
|
||||
guard conflicts.allSatisfy({ $0.databaseObject != nil }) else {
|
||||
assertionFailure("MergePolicy is only intended to work with database-level conflicts.")
|
||||
return try super.resolve(constraintConflicts: conflicts)
|
||||
}
|
||||
|
||||
for conflict in conflicts
|
||||
{
|
||||
switch conflict.databaseObject
|
||||
{
|
||||
case let databaseObject as App:
|
||||
// Delete previous permissions
|
||||
for permission in databaseObject.permissions
|
||||
{
|
||||
permission.managedObjectContext?.delete(permission)
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
try super.resolve(constraintConflicts: conflicts)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user