feat: add Core Data Stack

This commit is contained in:
CMK 2021-01-27 14:50:13 +08:00
parent b1f9047104
commit 1a000b96a8
30 changed files with 1620 additions and 39 deletions

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20C69" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="HomeTimelineIndex" representedClassName=".HomeTimelineIndex" syncable="YES">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="userIdentifier" attributeType="String"/>
<relationship name="toots" maxCount="1" deletionRule="Nullify" destinationEntity="Toots" inverseName="homeTimelineIndex" inverseEntity="Toots"/>
</entity>
<entity name="MastodonUser" representedClassName=".MastodonUser" syncable="YES">
<attribute name="acct" attributeType="String"/>
<attribute name="avatar" optional="YES" attributeType="String"/>
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String"/>
<attribute name="id" attributeType="String"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="username" attributeType="String"/>
<relationship name="toots" toMany="YES" deletionRule="Nullify" destinationEntity="Toots" inverseName="author" inverseEntity="Toots"/>
</entity>
<entity name="Toots" representedClassName=".Toots" syncable="YES">
<attribute name="content" attributeType="String"/>
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String"/>
<attribute name="id" attributeType="String"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="author" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="toots" inverseEntity="MastodonUser"/>
<relationship name="homeTimelineIndex" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="HomeTimelineIndex" inverseName="toots" inverseEntity="HomeTimelineIndex"/>
</entity>
<elements>
<element name="Toots" positionX="-248.4609375" positionY="17.3203125" width="128" height="163"/>
<element name="MastodonUser" positionX="9.34375" positionY="71.8828125" width="128" height="178"/>
<element name="HomeTimelineIndex" positionX="-108" positionY="135" width="128" height="118"/>
</elements>
</model>

View File

@ -0,0 +1,18 @@
//
// CoreDataStack.h
// CoreDataStack
//
// Created by MainasuK Cirno on 2021/1/27.
//
#import <Foundation/Foundation.h>
//! Project version number for CoreDataStack.
FOUNDATION_EXPORT double CoreDataStackVersionNumber;
//! Project version string for CoreDataStack.
FOUNDATION_EXPORT const unsigned char CoreDataStackVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <CoreDataStack/PublicHeader.h>

View File

@ -0,0 +1,101 @@
//
// CoreDataStack.swift
// CoreDataStack
//
// Created by Cirno MainasuK on 2021-1-27.
//
import os
import Foundation
import CoreData
public final class CoreDataStack {
private(set) var storeDescriptions: [NSPersistentStoreDescription]
init(persistentStoreDescriptions storeDescriptions: [NSPersistentStoreDescription]) {
self.storeDescriptions = storeDescriptions
}
public convenience init(databaseName: String = "shared") {
let storeURL = URL.storeURL(for: "group.com.joinmastodon.mastodon-temp", databaseName: databaseName)
let storeDescription = NSPersistentStoreDescription(url: storeURL)
self.init(persistentStoreDescriptions: [storeDescription])
}
public private(set) lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = CoreDataStack.persistentContainer()
CoreDataStack.configure(persistentContainer: container, storeDescriptions: storeDescriptions)
CoreDataStack.load(persistentContainer: container)
return container
}()
static func persistentContainer() -> NSPersistentContainer {
let bundles = [Bundle(for: Toots.self)]
guard let managedObjectModel = NSManagedObjectModel.mergedModel(from: bundles) else {
fatalError("cannot locate bundles")
}
let container = NSPersistentContainer(name: "CoreDataStack", managedObjectModel: managedObjectModel)
return container
}
static func configure(persistentContainer container: NSPersistentContainer, storeDescriptions: [NSPersistentStoreDescription]) {
container.persistentStoreDescriptions = storeDescriptions
}
static func load(persistentContainer container: NSPersistentContainer) {
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
if let reason = error.userInfo["reason"] as? String,
(reason == "Can't find mapping model for migration" || reason == "Persistent store migration failed, missing mapping model.") {
if let storeDescription = container.persistentStoreDescriptions.first, let url = storeDescription.url {
try? container.persistentStoreCoordinator.destroyPersistentStore(at: url, ofType: NSSQLiteStoreType, options: nil)
os_log("%{public}s[%{public}ld], %{public}s: cannot migrate model. rebuild database…", ((#file as NSString).lastPathComponent), #line, #function)
} else {
assertionFailure()
}
}
fatalError("Unresolved error \(error), \(error.userInfo)")
}
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
// it's looks like the remote notification only trigger when app enter and leave background
container.viewContext.automaticallyMergesChangesFromParent = true
os_log("%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, storeDescription.debugDescription)
})
}
}
extension CoreDataStack {
public func rebuild() {
let oldStoreURL = persistentContainer.persistentStoreCoordinator.url(for: persistentContainer.persistentStoreCoordinator.persistentStores.first!)
try! persistentContainer.persistentStoreCoordinator.destroyPersistentStore(at: oldStoreURL, ofType: NSSQLiteStoreType, options: nil)
CoreDataStack.load(persistentContainer: persistentContainer)
}
}

View File

@ -0,0 +1,64 @@
//
// HomeTimelineIndex.swift
// CoreDataStack
//
// Created by MainasuK Cirno on 2021/1/27.
//
import Foundation
import CoreData
final class HomeTimelineIndex: NSManagedObject {
public typealias ID = String
@NSManaged public private(set) var identifier: ID
@NSManaged public private(set) var domain: String
@NSManaged public private(set) var userIdentifier: String
@NSManaged public private(set) var createdAt: Date
// many-to-one relationship
@NSManaged public private(set) var toots: Toots
}
extension HomeTimelineIndex {
@discardableResult
public static func insert(
into context: NSManagedObjectContext,
property: Property,
toots: Toots
) -> HomeTimelineIndex {
let index: HomeTimelineIndex = context.insertObject()
index.identifier = property.identifier
index.domain = property.domain
index.userIdentifier = toots.author.identifier
index.createdAt = toots.createdAt
index.toots = toots
return index
}
}
extension HomeTimelineIndex {
public struct Property {
public let identifier: String
public let domain: String
public init(domain: String) {
self.identifier = UUID().uuidString + "@" + domain
self.domain = domain
}
}
}
extension HomeTimelineIndex: Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] {
return [NSSortDescriptor(keyPath: \HomeTimelineIndex.createdAt, ascending: false)]
}
}

View File

@ -0,0 +1,96 @@
//
// MastodonUser.swift
// CoreDataStack
//
// Created by MainasuK Cirno on 2021/1/27.
//
import Foundation
import CoreData
final class MastodonUser: NSManagedObject {
public typealias ID = String
@NSManaged public private(set) var identifier: ID
@NSManaged public private(set) var domain: String
@NSManaged public private(set) var id: String
@NSManaged public private(set) var acct: String
@NSManaged public private(set) var username: String
@NSManaged public private(set) var displayName: String?
@NSManaged public private(set) var createdAt: Date
@NSManaged public private(set) var updatedAt: Date
@NSManaged public private(set) var toots: Set<Toots>?
}
extension MastodonUser {
@discardableResult
public static func insert(
into context: NSManagedObjectContext,
property: Property
) -> MastodonUser {
let user: MastodonUser = context.insertObject()
user.identifier = property.identifier
user.domain = property.domain
user.id = property.id
user.acct = property.acct
user.username = property.username
user.displayName = property.displayName
user.createdAt = property.createdAt
user.updatedAt = property.networkDate
return user
}
}
extension MastodonUser {
public struct Property {
public let identifier: String
public let domain: String
public let id: String
public let acct: String
public let username: String
public let displayName: String?
public let createdAt: Date
public let networkDate: Date
public init(
id: String,
domain: String,
acct: String,
username: String,
displayName: String?,
content: String,
createdAt: Date,
networkDate: Date
) {
self.identifier = id + "@" + domain
self.domain = domain
self.id = id
self.acct = acct
self.username = username
self.displayName = displayName.flatMap { displayName in
return displayName.isEmpty ? nil : displayName
}
self.createdAt = createdAt
self.networkDate = networkDate
}
}
}
extension MastodonUser: Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] {
return [NSSortDescriptor(keyPath: \MastodonUser.createdAt, ascending: false)]
}
}

View File

@ -0,0 +1,88 @@
//
// Toots.swift
// CoreDataStack
//
// Created by MainasuK Cirno on 2021/1/27.
//
import Foundation
import CoreData
final class Toots: NSManagedObject {
public typealias ID = String
@NSManaged public private(set) var identifier: ID
@NSManaged public private(set) var domain: String
@NSManaged public private(set) var id: String
@NSManaged public private(set) var content: String
@NSManaged public private(set) var createdAt: Date
@NSManaged public private(set) var updatedAt: Date
// many-to-one relationship
@NSManaged public private(set) var author: MastodonUser
// one-to-many relationship
@NSManaged public private(set) var homeTimelineIndexes: Set<HomeTimelineIndex>?
}
extension Toots {
@discardableResult
public static func insert(
into context: NSManagedObjectContext,
property: Property,
author: MastodonUser
) -> Toots {
let toots: Toots = context.insertObject()
toots.identifier = property.identifier
toots.domain = property.domain
toots.id = property.id
toots.content = property.content
toots.createdAt = property.createdAt
toots.updatedAt = property.networkDate
toots.author = author
return toots
}
}
extension Toots {
public struct Property {
public let identifier: String
public let domain: String
public let id: String
public let content: String
public let createdAt: Date
public let networkDate: Date
public init(
id: String,
domain: String,
content: String,
createdAt: Date,
networkDate: Date
) {
self.identifier = id + "@" + domain
self.domain = domain
self.id = id
self.content = content
self.createdAt = createdAt
self.networkDate = networkDate
}
}
}
extension Toots: Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] {
return [NSSortDescriptor(keyPath: \Toots.createdAt, ascending: false)]
}
}

View File

@ -0,0 +1,30 @@
//
// Collection.swift
// CoreDataStack
//
// Created by Cirno MainasuK on 2020-10-14.
// Copyright © 2020 Twidere. All rights reserved.
//
import Foundation
import CoreData
extension Collection where Iterator.Element: NSManagedObject {
public func fetchFaults() {
guard !self.isEmpty else { return }
guard let context = self.first?.managedObjectContext else {
fatalError("Managed object must have context")
}
let faults = self.filter { $0.isFault }
guard let object = faults.first else { return }
let request = NSFetchRequest<Iterator.Element>()
request.entity = object.entity
request.returnsObjectsAsFaults = false
request.predicate = NSPredicate(format: "self in %@", faults)
do {
let _ = try context.fetch(request)
} catch {
assertionFailure(error.localizedDescription)
}
}
}

View File

@ -0,0 +1,50 @@
//
// NSManagedObjectContext.swift
// CoreDataStack
//
// Created by Cirno MainasuK on 2020-8-10.
// Copyright © 2020 Dimension. All rights reserved.
//
import os
import Foundation
import Combine
import CoreData
extension NSManagedObjectContext {
public func insert<T: NSManagedObject>() -> T where T: Managed {
guard let object = NSEntityDescription.insertNewObject(forEntityName: T.entityName, into: self) as? T else {
fatalError("cannot insert object: \(T.self)")
}
return object
}
public func saveOrRollback() throws {
do {
guard hasChanges else {
return
}
try save()
} catch {
rollback()
os_log("%{public}s[%{public}ld], %{public}s: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
throw error
}
}
public func performChanges(block: @escaping () -> Void) -> Future<Result<Void, Error>, Never> {
Future { promise in
self.perform {
block()
do {
try self.saveOrRollback()
promise(.success(Result.success(())))
} catch {
promise(.success(Result.failure(error)))
}
}
}
}
}

View File

@ -0,0 +1,23 @@
//
// URL.swift
// CoreDataStack
//
// Created by Cirno MainasuK on 2021-1-27.
//
import Foundation
public extension URL {
/// Returns a URL for the given app group and database pointing to the sqlite database.
static func storeURL(for appGroup: String, databaseName: String) -> URL {
guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
fatalError("Shared file container could not be created.")
}
return fileContainer
.appendingPathComponent("Databases", isDirectory: true)
.appendingPathComponent("\(databaseName).sqlite")
}
}

22
CoreDataStack/Info.plist Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View File

@ -0,0 +1,82 @@
//
// Managed.swift
// CoreDataStack
//
// Created by Cirno MainasuK on 2020-8-6.
// Copyright © 2020 Dimension. All rights reserved.
//
import Foundation
import CoreData
public protocol Managed: class, NSFetchRequestResult {
static var entityName: String { get }
static var defaultSortDescriptors: [NSSortDescriptor] { get }
}
extension Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] {
return []
}
public static var sortedFetchRequest: NSFetchRequest<Self> {
let request = NSFetchRequest<Self>(entityName: entityName)
request.sortDescriptors = defaultSortDescriptors
return request
}
}
extension NSManagedObjectContext {
public func insertObject<T: NSManagedObject>() -> T where T: Managed {
guard let object = NSEntityDescription.insertNewObject(forEntityName: T.entityName, into: self) as? T else {
fatalError("Wrong object type")
}
return object
}
}
extension Managed where Self: NSManagedObject {
public static var entityName: String { return entity().name! }
}
extension Managed where Self: NSManagedObject {
public static func findOrCreate(in context: NSManagedObjectContext, matching predicate: NSPredicate, configure: (Self) -> Void) -> Self {
guard let object = findOrFetch(in: context, matching: predicate) else {
let newObject: Self = context.insertObject()
configure(newObject)
return newObject
}
return object
}
public static func findOrFetch(in context: NSManagedObjectContext, matching predicate: NSPredicate) -> Self? {
guard let object = materializedObject(in: context, matching: predicate) else {
return fetch(in: context) { request in
request.predicate = predicate
request.returnsObjectsAsFaults = false
request.fetchLimit = 1
}.first
}
return object
}
public static func materializedObject(in context: NSManagedObjectContext, matching predicate: NSPredicate) -> Self? {
for object in context.registeredObjects where !object.isFault {
guard let result = object as? Self, predicate.evaluate(with: result) else { continue }
return result
}
return nil
}
public static func fetch(in context: NSManagedObjectContext, configurationBlock: (NSFetchRequest<Self>) -> Void = { _ in }) -> [Self] {
let request = NSFetchRequest<Self>(entityName: Self.entityName)
configurationBlock(request)
return try! context.fetch(request)
}
}

View File

@ -0,0 +1,12 @@
//
// NetworkUpdatable.swift
// CoreDataStack
//
// Created by Cirno MainasuK on 2020-9-4.
//
import Foundation
public protocol NetworkUpdatable {
var networkDate: Date { get }
}

View File

@ -0,0 +1,33 @@
//
// CoreDataStackTests.swift
// CoreDataStackTests
//
// Created by MainasuK Cirno on 2021/1/27.
//
import XCTest
@testable import CoreDataStack
class CoreDataStackTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -12,7 +12,6 @@
5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; };
7A9135D4559750AF07CA9BE4 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 602D783BEC22881EBAD84419 /* Pods_Mastodon.framework */; };
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; };
DB3D100025BAA6DA00EAA174 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3D0FFF25BAA6DA00EAA174 /* ViewController.swift */; };
DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; };
DB3D102425BAA7B400EAA174 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3D102225BAA7B400EAA174 /* Assets.swift */; };
DB3D102525BAA7B400EAA174 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB3D102325BAA7B400EAA174 /* Strings.swift */; };
@ -23,6 +22,29 @@
DB427DE225BAA00100D1B89D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DB427DE025BAA00100D1B89D /* LaunchScreen.storyboard */; };
DB427DED25BAA00100D1B89D /* MastodonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DEC25BAA00100D1B89D /* MastodonTests.swift */; };
DB427DF825BAA00100D1B89D /* MastodonUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DF725BAA00100D1B89D /* MastodonUITests.swift */; };
DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; };
DB89B9FE25C10FD0008580ED /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */; };
DB89BA0025C10FD0008580ED /* CoreDataStack.h in Headers */ = {isa = PBXBuildFile; fileRef = DB89B9F025C10FD0008580ED /* CoreDataStack.h */; settings = {ATTRIBUTES = (Public, ); }; };
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; };
DB89BA0425C10FD0008580ED /* CoreDataStack.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
DB89BA1225C1105C008580ED /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89BA1125C1105C008580ED /* CoreDataStack.swift */; };
DB89BA1B25C1107F008580ED /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89BA1825C1107F008580ED /* Collection.swift */; };
DB89BA1C25C1107F008580ED /* NSManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89BA1925C1107F008580ED /* NSManagedObjectContext.swift */; };
DB89BA1D25C1107F008580ED /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89BA1A25C1107F008580ED /* URL.swift */; };
DB89BA2725C110B4008580ED /* Toots.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89BA2625C110B4008580ED /* Toots.swift */; };
DB89BA3725C1145C008580ED /* CoreData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DB89BA3525C1145C008580ED /* CoreData.xcdatamodeld */; };
DB89BA4325C1165F008580ED /* NetworkUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89BA4125C1165F008580ED /* NetworkUpdatable.swift */; };
DB89BA4425C1165F008580ED /* Managed.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89BA4225C1165F008580ED /* Managed.swift */; };
DB8AF52525C131D1002E6C99 /* MastodonUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52425C131D1002E6C99 /* MastodonUser.swift */; };
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */; };
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52C25C13561002E6C99 /* DocumentStore.swift */; };
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF52D25C13561002E6C99 /* AppContext.swift */; };
DB8AF54425C13647002E6C99 /* SceneCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54225C13647002E6C99 /* SceneCoordinator.swift */; };
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54325C13647002E6C99 /* NeedsDependency.swift */; };
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */; };
DB8AF55725C137A8002E6C99 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55625C137A8002E6C99 /* HomeViewController.swift */; };
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; };
DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -40,8 +62,43 @@
remoteGlobalIDString = DB427DD125BAA00100D1B89D;
remoteInfo = Mastodon;
};
DB89B9F825C10FD0008580ED /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DB427DCA25BAA00100D1B89D /* Project object */;
proxyType = 1;
remoteGlobalIDString = DB89B9ED25C10FD0008580ED;
remoteInfo = CoreDataStack;
};
DB89B9FA25C10FD0008580ED /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DB427DCA25BAA00100D1B89D /* Project object */;
proxyType = 1;
remoteGlobalIDString = DB427DD125BAA00100D1B89D;
remoteInfo = Mastodon;
};
DB89BA0125C10FD0008580ED /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DB427DCA25BAA00100D1B89D /* Project object */;
proxyType = 1;
remoteGlobalIDString = DB89B9ED25C10FD0008580ED;
remoteInfo = CoreDataStack;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
DB89BA0825C10FD0008580ED /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
DB89BA0425C10FD0008580ED /* CoreDataStack.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon/Pods-Mastodon.debug.xcconfig"; sourceTree = "<group>"; };
459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.debug.xcconfig"; sourceTree = "<group>"; };
@ -52,7 +109,6 @@
BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.release.xcconfig"; sourceTree = "<group>"; };
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; };
DB3D0FFF25BAA6DA00EAA174 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
DB3D102225BAA7B400EAA174 /* Assets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = "<group>"; };
DB3D102325BAA7B400EAA174 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
@ -69,6 +125,31 @@
DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MastodonUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DB427DF725BAA00100D1B89D /* MastodonUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUITests.swift; sourceTree = "<group>"; };
DB427DF925BAA00100D1B89D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DB89B9F025C10FD0008580ED /* CoreDataStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreDataStack.h; sourceTree = "<group>"; };
DB89B9F125C10FD0008580ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DB89B9F625C10FD0008580ED /* CoreDataStackTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreDataStackTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStackTests.swift; sourceTree = "<group>"; };
DB89B9FF25C10FD0008580ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mastodon.entitlements; sourceTree = "<group>"; };
DB89BA1125C1105C008580ED /* CoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStack.swift; sourceTree = "<group>"; };
DB89BA1825C1107F008580ED /* Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = "<group>"; };
DB89BA1925C1107F008580ED /* NSManagedObjectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSManagedObjectContext.swift; sourceTree = "<group>"; };
DB89BA1A25C1107F008580ED /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
DB89BA2625C110B4008580ED /* Toots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toots.swift; sourceTree = "<group>"; };
DB89BA3625C1145C008580ED /* CoreData.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreData.xcdatamodel; sourceTree = "<group>"; };
DB89BA4125C1165F008580ED /* NetworkUpdatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkUpdatable.swift; sourceTree = "<group>"; };
DB89BA4225C1165F008580ED /* Managed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Managed.swift; sourceTree = "<group>"; };
DB8AF52425C131D1002E6C99 /* MastodonUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonUser.swift; sourceTree = "<group>"; };
DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewStateStore.swift; sourceTree = "<group>"; };
DB8AF52C25C13561002E6C99 /* DocumentStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentStore.swift; sourceTree = "<group>"; };
DB8AF52D25C13561002E6C99 /* AppContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = "<group>"; };
DB8AF54225C13647002E6C99 /* SceneCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneCoordinator.swift; sourceTree = "<group>"; };
DB8AF54325C13647002E6C99 /* NeedsDependency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeedsDependency.swift; sourceTree = "<group>"; };
DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = "<group>"; };
DB8AF55625C137A8002E6C99 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineIndex.swift; sourceTree = "<group>"; };
EC6E707B68A67DB08EC288FA /* Pods-MastodonTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.debug.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -77,6 +158,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */,
7A9135D4559750AF07CA9BE4 /* Pods_Mastodon.framework in Frameworks */,
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */,
@ -99,6 +181,21 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DB89B9EB25C10FD0008580ED /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
DB89B9F325C10FD0008580ED /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -161,6 +258,8 @@
DB427DD425BAA00100D1B89D /* Mastodon */,
DB427DEB25BAA00100D1B89D /* MastodonTests */,
DB427DF625BAA00100D1B89D /* MastodonUITests */,
DB89B9EF25C10FD0008580ED /* CoreDataStack */,
DB89B9FC25C10FD0008580ED /* CoreDataStackTests */,
DB427DD325BAA00100D1B89D /* Products */,
1EBA4F56E920856A3FC84ACB /* Pods */,
4E8E8B18DB8471A676012CF9 /* Frameworks */,
@ -173,6 +272,8 @@
DB427DD225BAA00100D1B89D /* Mastodon.app */,
DB427DE825BAA00100D1B89D /* MastodonTests.xctest */,
DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */,
DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */,
DB89B9F625C10FD0008580ED /* CoreDataStackTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@ -180,8 +281,12 @@
DB427DD425BAA00100D1B89D /* Mastodon */ = {
isa = PBXGroup;
children = (
DB89BA1025C10FF5008580ED /* Mastodon.entitlements */,
DB427DE325BAA00100D1B89D /* Info.plist */,
DB3D0FFF25BAA6DA00EAA174 /* ViewController.swift */,
DB8AF52A25C13561002E6C99 /* State */,
DB8AF56225C138BC002E6C99 /* Extension */,
DB8AF55525C1379F002E6C99 /* Scene */,
DB8AF54125C13647002E6C99 /* Coordinator */,
DB3D101B25BAA79200EAA174 /* Generated */,
DB3D0FF825BAA6B200EAA174 /* Resources */,
DB3D0FF725BAA68500EAA174 /* Supporting Files */,
@ -207,8 +312,115 @@
path = MastodonUITests;
sourceTree = "<group>";
};
DB89B9EF25C10FD0008580ED /* CoreDataStack */ = {
isa = PBXGroup;
children = (
DB89B9F125C10FD0008580ED /* Info.plist */,
DB89B9F025C10FD0008580ED /* CoreDataStack.h */,
DB89BA1125C1105C008580ED /* CoreDataStack.swift */,
DB89BA3525C1145C008580ED /* CoreData.xcdatamodeld */,
DB89BA4025C1165F008580ED /* Protocol */,
DB89BA1725C1107F008580ED /* Extension */,
DB89BA2C25C110B7008580ED /* Entity */,
);
path = CoreDataStack;
sourceTree = "<group>";
};
DB89B9FC25C10FD0008580ED /* CoreDataStackTests */ = {
isa = PBXGroup;
children = (
DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */,
DB89B9FF25C10FD0008580ED /* Info.plist */,
);
path = CoreDataStackTests;
sourceTree = "<group>";
};
DB89BA1725C1107F008580ED /* Extension */ = {
isa = PBXGroup;
children = (
DB89BA1825C1107F008580ED /* Collection.swift */,
DB89BA1925C1107F008580ED /* NSManagedObjectContext.swift */,
DB89BA1A25C1107F008580ED /* URL.swift */,
);
path = Extension;
sourceTree = "<group>";
};
DB89BA2C25C110B7008580ED /* Entity */ = {
isa = PBXGroup;
children = (
DB89BA2625C110B4008580ED /* Toots.swift */,
DB8AF52425C131D1002E6C99 /* MastodonUser.swift */,
DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */,
);
path = Entity;
sourceTree = "<group>";
};
DB89BA4025C1165F008580ED /* Protocol */ = {
isa = PBXGroup;
children = (
DB89BA4125C1165F008580ED /* NetworkUpdatable.swift */,
DB89BA4225C1165F008580ED /* Managed.swift */,
);
path = Protocol;
sourceTree = "<group>";
};
DB8AF52A25C13561002E6C99 /* State */ = {
isa = PBXGroup;
children = (
DB8AF52B25C13561002E6C99 /* ViewStateStore.swift */,
DB8AF52C25C13561002E6C99 /* DocumentStore.swift */,
DB8AF52D25C13561002E6C99 /* AppContext.swift */,
);
path = State;
sourceTree = "<group>";
};
DB8AF54125C13647002E6C99 /* Coordinator */ = {
isa = PBXGroup;
children = (
DB8AF54225C13647002E6C99 /* SceneCoordinator.swift */,
DB8AF54325C13647002E6C99 /* NeedsDependency.swift */,
);
path = Coordinator;
sourceTree = "<group>";
};
DB8AF54E25C13703002E6C99 /* MainTab */ = {
isa = PBXGroup;
children = (
DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */,
);
path = MainTab;
sourceTree = "<group>";
};
DB8AF55525C1379F002E6C99 /* Scene */ = {
isa = PBXGroup;
children = (
DB8AF54E25C13703002E6C99 /* MainTab */,
DB8AF55625C137A8002E6C99 /* HomeViewController.swift */,
);
path = Scene;
sourceTree = "<group>";
};
DB8AF56225C138BC002E6C99 /* Extension */ = {
isa = PBXGroup;
children = (
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */,
);
path = Extension;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
DB89B9E925C10FD0008580ED /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DB89BA0025C10FD0008580ED /* CoreDataStack.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
DB427DD125BAA00100D1B89D /* Mastodon */ = {
isa = PBXNativeTarget;
@ -220,10 +432,12 @@
DB427DD025BAA00100D1B89D /* Resources */,
5532CB85BBE168B25B20720B /* [CP] Embed Pods Frameworks */,
DB3D100425BAA71500EAA174 /* ShellScript */,
DB89BA0825C10FD0008580ED /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
DB89BA0225C10FD0008580ED /* PBXTargetDependency */,
);
name = Mastodon;
packageProductDependencies = (
@ -273,6 +487,43 @@
productReference = DB427DF325BAA00100D1B89D /* MastodonUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
DB89B9ED25C10FD0008580ED /* CoreDataStack */ = {
isa = PBXNativeTarget;
buildConfigurationList = DB89BA0525C10FD0008580ED /* Build configuration list for PBXNativeTarget "CoreDataStack" */;
buildPhases = (
DB89B9E925C10FD0008580ED /* Headers */,
DB89B9EA25C10FD0008580ED /* Sources */,
DB89B9EB25C10FD0008580ED /* Frameworks */,
DB89B9EC25C10FD0008580ED /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = CoreDataStack;
productName = CoreDataStack;
productReference = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */;
productType = "com.apple.product-type.framework";
};
DB89B9F525C10FD0008580ED /* CoreDataStackTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = DB89BA0925C10FD0008580ED /* Build configuration list for PBXNativeTarget "CoreDataStackTests" */;
buildPhases = (
DB89B9F225C10FD0008580ED /* Sources */,
DB89B9F325C10FD0008580ED /* Frameworks */,
DB89B9F425C10FD0008580ED /* Resources */,
);
buildRules = (
);
dependencies = (
DB89B9F925C10FD0008580ED /* PBXTargetDependency */,
DB89B9FB25C10FD0008580ED /* PBXTargetDependency */,
);
name = CoreDataStackTests;
productName = CoreDataStackTests;
productReference = DB89B9F625C10FD0008580ED /* CoreDataStackTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -293,6 +544,14 @@
CreatedOnToolsVersion = 12.4;
TestTargetID = DB427DD125BAA00100D1B89D;
};
DB89B9ED25C10FD0008580ED = {
CreatedOnToolsVersion = 12.4;
LastSwiftMigration = 1240;
};
DB89B9F525C10FD0008580ED = {
CreatedOnToolsVersion = 12.4;
TestTargetID = DB427DD125BAA00100D1B89D;
};
};
};
buildConfigurationList = DB427DCD25BAA00100D1B89D /* Build configuration list for PBXProject "Mastodon" */;
@ -314,6 +573,8 @@
DB427DD125BAA00100D1B89D /* Mastodon */,
DB427DE725BAA00100D1B89D /* MastodonTests */,
DB427DF225BAA00100D1B89D /* MastodonUITests */,
DB89B9ED25C10FD0008580ED /* CoreDataStack */,
DB89B9F525C10FD0008580ED /* CoreDataStackTests */,
);
};
/* End PBXProject section */
@ -344,6 +605,20 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DB89B9EC25C10FD0008580ED /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
DB89B9F425C10FD0008580ED /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@ -471,8 +746,15 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DB3D100025BAA6DA00EAA174 /* ViewController.swift in Sources */,
DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */,
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */,
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */,
DB8AF54425C13647002E6C99 /* SceneCoordinator.swift in Sources */,
DB8AF52E25C13561002E6C99 /* ViewStateStore.swift in Sources */,
DB8AF55725C137A8002E6C99 /* HomeViewController.swift in Sources */,
DB3D102525BAA7B400EAA174 /* Strings.swift in Sources */,
DB3D102425BAA7B400EAA174 /* Assets.swift in Sources */,
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */,
@ -495,6 +777,31 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
DB89B9EA25C10FD0008580ED /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DB89BA1225C1105C008580ED /* CoreDataStack.swift in Sources */,
DB89BA1C25C1107F008580ED /* NSManagedObjectContext.swift in Sources */,
DB89BA3725C1145C008580ED /* CoreData.xcdatamodeld in Sources */,
DB8AF52525C131D1002E6C99 /* MastodonUser.swift in Sources */,
DB89BA1B25C1107F008580ED /* Collection.swift in Sources */,
DB89BA2725C110B4008580ED /* Toots.swift in Sources */,
DB89BA4425C1165F008580ED /* Managed.swift in Sources */,
DB89BA4325C1165F008580ED /* NetworkUpdatable.swift in Sources */,
DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */,
DB89BA1D25C1107F008580ED /* URL.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DB89B9F225C10FD0008580ED /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DB89B9FE25C10FD0008580ED /* CoreDataStackTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -508,6 +815,21 @@
target = DB427DD125BAA00100D1B89D /* Mastodon */;
targetProxy = DB427DF425BAA00100D1B89D /* PBXContainerItemProxy */;
};
DB89B9F925C10FD0008580ED /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DB89B9ED25C10FD0008580ED /* CoreDataStack */;
targetProxy = DB89B9F825C10FD0008580ED /* PBXContainerItemProxy */;
};
DB89B9FB25C10FD0008580ED /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DB427DD125BAA00100D1B89D /* Mastodon */;
targetProxy = DB89B9FA25C10FD0008580ED /* PBXContainerItemProxy */;
};
DB89BA0225C10FD0008580ED /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DB89B9ED25C10FD0008580ED /* CoreDataStack */;
targetProxy = DB89BA0125C10FD0008580ED /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@ -589,7 +911,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@ -644,7 +966,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
@ -658,8 +980,10 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 2E1F6A67FDF9771D3E064FDC /* Pods-Mastodon.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = Mastodon/Info.plist;
@ -679,8 +1003,10 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 75E3471C898DDD9631729B6E /* Pods-Mastodon.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Mastodon/Mastodon.entitlements;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = Mastodon/Info.plist;
@ -780,6 +1106,103 @@
};
name = Release;
};
DB89BA0625C10FD0008580ED /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 7LFDZ96332;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = CoreDataStack/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.CoreDataStack;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
DB89BA0725C10FD0008580ED /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 7LFDZ96332;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = CoreDataStack/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.CoreDataStack;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
DB89BA0A25C10FD0008580ED /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = CoreDataStackTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.CoreDataStackTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mastodon.app/Mastodon";
};
name = Debug;
};
DB89BA0B25C10FD0008580ED /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 7LFDZ96332;
INFOPLIST_FILE = CoreDataStackTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = org.joinmastodon.CoreDataStackTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mastodon.app/Mastodon";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -819,6 +1242,24 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DB89BA0525C10FD0008580ED /* Build configuration list for PBXNativeTarget "CoreDataStack" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DB89BA0625C10FD0008580ED /* Debug */,
DB89BA0725C10FD0008580ED /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
DB89BA0925C10FD0008580ED /* Build configuration list for PBXNativeTarget "CoreDataStackTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DB89BA0A25C10FD0008580ED /* Debug */,
DB89BA0B25C10FD0008580ED /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
@ -843,6 +1284,19 @@
productName = AlamofireImage;
};
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */
DB89BA3525C1145C008580ED /* CoreData.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DB89BA3625C1145C008580ED /* CoreData.xcdatamodel */,
);
currentVersion = DB89BA3625C1145C008580ED /* CoreData.xcdatamodel */;
path = CoreData.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = DB427DCA25BAA00100D1B89D /* Project object */;
}

View File

@ -4,6 +4,11 @@
<dict>
<key>SchemeUserState</key>
<dict>
<key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>6</integer>
</dict>
<key>Mastodon.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>

View File

@ -0,0 +1,28 @@
//
// NeedsDependency.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
//
import UIKit
protocol NeedsDependency: class {
var context: AppContext! { get set }
var coordinator: SceneCoordinator! { get set }
}
extension UISceneSession {
private struct AssociatedKeys {
static var sceneCoordinator = "SceneCoordinator"
}
weak var sceneCoordinator: SceneCoordinator? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.sceneCoordinator) as? SceneCoordinator
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.sceneCoordinator, newValue, .OBJC_ASSOCIATION_ASSIGN)
}
}
}

View File

@ -0,0 +1,124 @@
//
// SceneCoordinator.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
import UIKit
import SafariServices
final public class SceneCoordinator {
private weak var scene: UIScene!
private weak var sceneDelegate: SceneDelegate!
private weak var appContext: AppContext!
let id = UUID().uuidString
init(scene: UIScene, sceneDelegate: SceneDelegate, appContext: AppContext) {
self.scene = scene
self.sceneDelegate = sceneDelegate
self.appContext = appContext
scene.session.sceneCoordinator = self
}
}
extension SceneCoordinator {
enum Transition {
case show // push
case showDetail // replace
case modal(animated: Bool, completion: (() -> Void)? = nil)
case custom(transitioningDelegate: UIViewControllerTransitioningDelegate)
case customPush
case safariPresent(animated: Bool, completion: (() -> Void)? = nil)
case activityViewControllerPresent(animated: Bool, completion: (() -> Void)? = nil)
case alertController(animated: Bool, completion: (() -> Void)? = nil)
}
enum Scene {
}
}
extension SceneCoordinator {
func setup() {
let viewController = MainTabBarController(context: appContext, coordinator: self)
sceneDelegate.window?.rootViewController = viewController
}
@discardableResult
func present(scene: Scene, from sender: UIViewController?, transition: Transition) -> UIViewController? {
guard let viewController = get(scene: scene) else {
return nil
}
guard var presentingViewController = sender ?? sceneDelegate.window?.rootViewController?.topMost else {
return nil
}
if let mainTabBarController = presentingViewController as? MainTabBarController,
let navigationController = mainTabBarController.selectedViewController as? UINavigationController,
let topViewController = navigationController.topViewController {
presentingViewController = topViewController
}
switch transition {
case .show:
presentingViewController.show(viewController, sender: sender)
case .showDetail:
let navigationController = UINavigationController(rootViewController: viewController)
presentingViewController.showDetailViewController(navigationController, sender: sender)
case .modal(let animated, let completion):
let modalNavigationController = UINavigationController(rootViewController: viewController)
if let adaptivePresentationControllerDelegate = viewController as? UIAdaptivePresentationControllerDelegate {
modalNavigationController.presentationController?.delegate = adaptivePresentationControllerDelegate
}
presentingViewController.present(modalNavigationController, animated: animated, completion: completion)
case .custom(let transitioningDelegate):
viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitioningDelegate
sender?.present(viewController, animated: true, completion: nil)
case .customPush:
// set delegate in view controller
assert(sender?.navigationController?.delegate != nil)
sender?.navigationController?.pushViewController(viewController, animated: true)
case .safariPresent(let animated, let completion):
presentingViewController.present(viewController, animated: animated, completion: completion)
case .activityViewControllerPresent(let animated, let completion):
presentingViewController.present(viewController, animated: animated, completion: completion)
case .alertController(let animated, let completion):
presentingViewController.present(viewController, animated: animated, completion: completion)
}
return viewController
}
}
private extension SceneCoordinator {
func get(scene: Scene) -> UIViewController? {
let viewController: UIViewController?
// TODO:
viewController = nil
setupDependency(for: viewController as? NeedsDependency)
return viewController
}
private func setupDependency(for needs: NeedsDependency?) {
needs?.context = appContext
needs?.coordinator = self
}
}

View File

@ -0,0 +1,67 @@
//
// UIViewController.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
//
import UIKit
extension UIViewController {
/// Returns the top most view controller from given view controller's stack.
var topMost: UIViewController? {
// presented view controller
if let presentedViewController = presentedViewController {
return presentedViewController.topMost
}
// UITabBarController
if let tabBarController = self as? UITabBarController,
let selectedViewController = tabBarController.selectedViewController {
return selectedViewController.topMost
}
// UINavigationController
if let navigationController = self as? UINavigationController,
let visibleViewController = navigationController.visibleViewController {
return visibleViewController.topMost
}
// UIPageController
if let pageViewController = self as? UIPageViewController,
pageViewController.viewControllers?.count == 1 {
return pageViewController.viewControllers?.first?.topMost ?? self
}
// child view controller
for subview in self.view?.subviews ?? [] {
if let childViewController = subview.next as? UIViewController {
return childViewController.topMost
}
}
return self
}
}
extension UIViewController {
/// https://bluelemonbits.com/2018/08/26/inserting-cells-at-the-top-of-a-uitableview-with-no-scrolling/
static func topVisibleTableViewCellIndexPath(in tableView: UITableView, navigationBar: UINavigationBar) -> IndexPath? {
let navigationBarRectInTableView = tableView.convert(navigationBar.bounds, from: navigationBar)
let navigationBarMaxYPosition = CGPoint(x: 0, y: navigationBarRectInTableView.origin.y + navigationBarRectInTableView.size.height + 1) // +1pt for UIKit cell locate
let mostTopVisiableIndexPath = tableView.indexPathForRow(at: navigationBarMaxYPosition)
return mostTopVisiableIndexPath
}
static func tableViewCellOriginOffsetToWindowTop(in tableView: UITableView, at indexPath: IndexPath, navigationBar: UINavigationBar) -> CGFloat {
let rectForTopRow = tableView.rectForRow(at: indexPath)
let navigationBarRectInTableView = tableView.convert(navigationBar.bounds, from: navigationBar)
let navigationBarMaxYPosition = CGPoint(x: 0, y: navigationBarRectInTableView.origin.y + navigationBarRectInTableView.size.height) // without +1pt
let differenceBetweenTopRowAndNavigationBar = rectForTopRow.origin.y - navigationBarMaxYPosition.y
return differenceBetweenTopRowAndNavigationBar
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.joinmastodon.mastodon-temp</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,27 @@
//
// HomeViewController.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021/1/27.
//
import UIKit
final class HomeViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
}
extension HomeViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Home"
view.backgroundColor = .systemBackground
}
}

View File

@ -0,0 +1,89 @@
//
// MainTabBarController.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
//
import os.log
import UIKit
import Combine
import SafariServices
class MainTabBarController: UITabBarController {
var disposeBag = Set<AnyCancellable>()
weak var context: AppContext!
weak var coordinator: SceneCoordinator!
enum Tab: Int, CaseIterable {
case home
var title: String {
switch self {
case .home: return "Home"
}
}
var image: UIImage {
switch self {
case .home: return UIImage(systemName: "house")!
}
}
func viewController(context: AppContext, coordinator: SceneCoordinator) -> UIViewController {
let viewController: UIViewController
switch self {
case .home:
let _viewController = HomeViewController()
_viewController.context = context
_viewController.coordinator = coordinator
viewController = _viewController
}
viewController.title = self.title
return UINavigationController(rootViewController: viewController)
}
}
init(context: AppContext, coordinator: SceneCoordinator) {
self.context = context
self.coordinator = coordinator
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension MainTabBarController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let tabs = Tab.allCases
let viewControllers: [UIViewController] = tabs.map { tab in
let viewController = tab.viewController(context: context, coordinator: coordinator)
viewController.tabBarItem.title = "" // set text to empty string for image only style (SDK failed to layout when set to nil)
viewController.tabBarItem.image = tab.image
return viewController
}
setViewControllers(viewControllers, animated: false)
selectedIndex = 0
// TODO: custom accent color
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithDefaultBackground()
tabBar.standardAppearance = tabBarAppearance
#if DEBUG
// selectedIndex = 1
#endif
}
}

View File

@ -0,0 +1,56 @@
//
// AppContext.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
//
import os.log
import UIKit
import Combine
import CoreData
import CoreDataStack
class AppContext: ObservableObject {
var disposeBag = Set<AnyCancellable>()
@Published var viewStateStore = ViewStateStore()
let coreDataStack: CoreDataStack
let managedObjectContext: NSManagedObjectContext
let backgroundManagedObjectContext: NSManagedObjectContext
let documentStore: DocumentStore
private var documentStoreSubscription: AnyCancellable!
let overrideTraitCollection = CurrentValueSubject<UITraitCollection?, Never>(nil)
init() {
let _coreDataStack = CoreDataStack()
let _managedObjectContext = _coreDataStack.persistentContainer.viewContext
let _backgroundManagedObjectContext = _coreDataStack.persistentContainer.newBackgroundContext()
coreDataStack = _coreDataStack
managedObjectContext = _managedObjectContext
backgroundManagedObjectContext = _backgroundManagedObjectContext
documentStore = DocumentStore()
documentStoreSubscription = documentStore.objectWillChange
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
self.objectWillChange.send()
}
backgroundManagedObjectContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave, object: backgroundManagedObjectContext)
.sink { [weak self] notification in
guard let self = self else { return }
self.managedObjectContext.perform {
self.managedObjectContext.mergeChanges(fromContextDidSave: notification)
}
}
.store(in: &disposeBag)
}
}

View File

@ -0,0 +1,11 @@
//
// DocumentStore.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
//
import UIKit
import Combine
class DocumentStore: ObservableObject { }

View File

@ -0,0 +1,14 @@
//
// ViewStateStore.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-1-27.
//
import Combine
struct ViewStateStore {
}
enum ViewState { }

View File

@ -10,7 +10,7 @@ import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let appContext = AppContext()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
@ -34,3 +34,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
extension AppDelegate {
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
#if DEBUG
return .all
#else
return UIDevice.current.userInterfaceIdiom == .pad ? .all : .portrait
#endif
}
}
extension AppContext {
static var shared: AppContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.appContext
}
}

View File

@ -1,24 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="BYZ-38-t0r" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="226" y="166"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View File

@ -10,13 +10,22 @@ import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var coordinator: SceneCoordinator?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
self.window = window
let appContext = AppContext.shared
let sceneCoordinator = SceneCoordinator(scene: scene, sceneDelegate: self, appContext: appContext)
self.coordinator = sceneCoordinator
sceneCoordinator.setup()
window.makeKeyAndVisible()
}
func sceneDidDisconnect(_ scene: UIScene) {

View File

@ -1,21 +0,0 @@
//
// ViewController.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021/1/22.
//
import UIKit
import MastodonSDK
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}

View File

@ -9,10 +9,14 @@ import Combine
import Foundation
public extension Mastodon.API.App {
static func appEndpointURL(domain: String) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("apps")
}
}
extension Mastodon.API.App {
struct Application: Codable {