Compare commits
No commits in common. "1c9b1b9ac3a6b52fcce052de71a8a2ad93d202ff" and "5a098df931f51e46b62123a2ade17e7e7a27b415" have entirely different histories.
1c9b1b9ac3
...
5a098df931
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public final class Status: /*StatusProtocol,*/ Decodable {
|
public final class Status: StatusProtocol, Decodable {
|
||||||
public let id: String
|
public let id: String
|
||||||
public let uri: String
|
public let uri: String
|
||||||
public let url: URL?
|
public let url: URL?
|
||||||
|
@ -23,8 +23,8 @@ public final class Status: /*StatusProtocol,*/ Decodable {
|
||||||
// public let repliesCount: Int
|
// public let repliesCount: Int
|
||||||
public let reblogsCount: Int
|
public let reblogsCount: Int
|
||||||
public let favouritesCount: Int
|
public let favouritesCount: Int
|
||||||
public let reblogged: Bool?
|
public let reblogged: Bool
|
||||||
public let favourited: Bool?
|
public let favourited: Bool
|
||||||
public let muted: Bool?
|
public let muted: Bool?
|
||||||
public let sensitive: Bool
|
public let sensitive: Bool
|
||||||
public let spoilerText: String
|
public let spoilerText: String
|
||||||
|
|
|
@ -134,8 +134,6 @@
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||||
D64D8CA92463B494006B0BAA /* CachedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* CachedDictionary.swift */; };
|
D64D8CA92463B494006B0BAA /* CachedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* CachedDictionary.swift */; };
|
||||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
|
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
|
||||||
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */; };
|
|
||||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */; };
|
|
||||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; };
|
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; };
|
||||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
|
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
|
||||||
D65F613423AEAB6600F3CFD3 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65F613323AEAB6600F3CFD3 /* OnboardingTests.swift */; };
|
D65F613423AEAB6600F3CFD3 /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65F613323AEAB6600F3CFD3 /* OnboardingTests.swift */; };
|
||||||
|
@ -430,8 +428,6 @@
|
||||||
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
||||||
D64D8CA82463B494006B0BAA /* CachedDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedDictionary.swift; sourceTree = "<group>"; };
|
D64D8CA82463B494006B0BAA /* CachedDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedDictionary.swift; sourceTree = "<group>"; };
|
||||||
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
|
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
|
||||||
D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvAttachmentView.swift; sourceTree = "<group>"; };
|
|
||||||
D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvAttachmentViewController.swift; sourceTree = "<group>"; };
|
|
||||||
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSwipeActionProvider.swift; sourceTree = "<group>"; };
|
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSwipeActionProvider.swift; sourceTree = "<group>"; };
|
||||||
D65F612D23AE990C00F3CFD3 /* Embassy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Embassy.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D65F612D23AE990C00F3CFD3 /* Embassy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Embassy.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D65F613023AE99E000F3CFD3 /* Ambassador.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Ambassador.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D65F613023AE99E000F3CFD3 /* Ambassador.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Ambassador.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -601,7 +597,6 @@
|
||||||
children = (
|
children = (
|
||||||
04D14BAE22B34A2800642648 /* GalleryViewController.swift */,
|
04D14BAE22B34A2800642648 /* GalleryViewController.swift */,
|
||||||
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */,
|
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */,
|
||||||
D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */,
|
|
||||||
);
|
);
|
||||||
path = "Attachment Gallery";
|
path = "Attachment Gallery";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1221,7 +1216,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */,
|
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */,
|
||||||
D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */,
|
|
||||||
D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */,
|
D6C7D27C22B6EBF800071952 /* AttachmentsContainerView.swift */,
|
||||||
);
|
);
|
||||||
path = Attachments;
|
path = Attachments;
|
||||||
|
@ -1656,7 +1650,6 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */,
|
D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */,
|
||||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */,
|
|
||||||
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
|
||||||
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
D6DD353D22F28CD000A9563A /* ContentWarningCopyMode.swift in Sources */,
|
||||||
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
|
||||||
|
@ -1709,7 +1702,6 @@
|
||||||
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
D6B053A623BD2D0C00A066FA /* AssetCollectionViewController.swift in Sources */,
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
|
||||||
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */,
|
||||||
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */,
|
|
||||||
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */,
|
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */,
|
||||||
D6A3BC8F2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift in Sources */,
|
D6A3BC8F2321FFB900FD64D5 /* StatusActionAccountListTableViewController.swift in Sources */,
|
||||||
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */,
|
D6AEBB4823216B1D00E5038B /* AccountActivity.swift in Sources */,
|
||||||
|
|
|
@ -30,8 +30,7 @@ class MastodonController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let transient: Bool
|
private(set) lazy var persistentContainer = MastodonCachePersistentStore(for: self)
|
||||||
private(set) lazy var persistentContainer = MastodonCachePersistentStore(for: accountInfo, transient: transient)
|
|
||||||
|
|
||||||
let instanceURL: URL
|
let instanceURL: URL
|
||||||
var accountInfo: LocalData.UserAccountInfo?
|
var accountInfo: LocalData.UserAccountInfo?
|
||||||
|
@ -41,11 +40,10 @@ class MastodonController {
|
||||||
var account: Account!
|
var account: Account!
|
||||||
var instance: Instance!
|
var instance: Instance!
|
||||||
|
|
||||||
init(instanceURL: URL, transient: Bool = false) {
|
init(instanceURL: URL) {
|
||||||
self.instanceURL = instanceURL
|
self.instanceURL = instanceURL
|
||||||
self.accountInfo = nil
|
self.accountInfo = nil
|
||||||
self.client = Client(baseURL: instanceURL)
|
self.client = Client(baseURL: instanceURL)
|
||||||
self.transient = transient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func run<Result>(_ request: Request<Result>, completion: @escaping Client.Callback<Result>) {
|
func run<Result>(_ request: Request<Result>, completion: @escaping Client.Callback<Result>) {
|
||||||
|
@ -84,16 +82,8 @@ class MastodonController {
|
||||||
run(request) { response in
|
run(request) { response in
|
||||||
guard case let .success(account, _) = response else { fatalError() }
|
guard case let .success(account, _) = response else { fatalError() }
|
||||||
self.account = account
|
self.account = account
|
||||||
self.persistentContainer.backgroundContext.perform {
|
self.persistentContainer.addOrUpdate(account: account)
|
||||||
if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) {
|
completion?(account)
|
||||||
accountMO.updateFrom(apiAccount: account, container: self.persistentContainer)
|
|
||||||
} else {
|
|
||||||
// the first time the user's account is added to the store,
|
|
||||||
// increment its reference count so that it's never removed
|
|
||||||
self.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true)
|
|
||||||
}
|
|
||||||
completion?(account)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
|
||||||
@NSManaged public var locked: Bool
|
@NSManaged public var locked: Bool
|
||||||
@NSManaged public var movedCD: Bool
|
@NSManaged public var movedCD: Bool
|
||||||
@NSManaged public var note: String
|
@NSManaged public var note: String
|
||||||
@NSManaged public var referenceCount: Int
|
|
||||||
@NSManaged public var statusesCount: Int
|
@NSManaged public var statusesCount: Int
|
||||||
@NSManaged public var url: URL
|
@NSManaged public var url: URL
|
||||||
@NSManaged public var username: String
|
@NSManaged public var username: String
|
||||||
|
@ -47,30 +46,12 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
|
||||||
public var bot: Bool? { botCD }
|
public var bot: Bool? { botCD }
|
||||||
public var moved: Bool? { movedCD }
|
public var moved: Bool? { movedCD }
|
||||||
|
|
||||||
func incrementReferenceCount() {
|
|
||||||
referenceCount += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func decrementReferenceCount() {
|
|
||||||
referenceCount -= 1
|
|
||||||
if referenceCount <= 0 {
|
|
||||||
managedObjectContext!.delete(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override func prepareForDeletion() {
|
|
||||||
super.prepareForDeletion()
|
|
||||||
movedTo?.decrementReferenceCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AccountMO {
|
extension AccountMO {
|
||||||
convenience init(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore, context: NSManagedObjectContext) {
|
convenience init(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore, context: NSManagedObjectContext) {
|
||||||
self.init(context: context)
|
self.init(context: context)
|
||||||
self.updateFrom(apiAccount: account, container: container)
|
self.updateFrom(apiAccount: account, container: container)
|
||||||
|
|
||||||
movedTo?.incrementReferenceCount()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFrom(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore) {
|
func updateFrom(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore) {
|
||||||
|
|
|
@ -23,17 +23,8 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
let statusSubject = PassthroughSubject<String, Never>()
|
let statusSubject = PassthroughSubject<String, Never>()
|
||||||
let accountSubject = PassthroughSubject<String, Never>()
|
let accountSubject = PassthroughSubject<String, Never>()
|
||||||
|
|
||||||
init(for accountInfo: LocalData.UserAccountInfo?, transient: Bool = false) {
|
init(for controller: MastodonController) {
|
||||||
if transient {
|
super.init(name: "\(controller.accountInfo!.id)_cache", managedObjectModel: MastodonCachePersistentStore.managedObjectModel)
|
||||||
super.init(name: "transient_cache", managedObjectModel: MastodonCachePersistentStore.managedObjectModel)
|
|
||||||
|
|
||||||
let storeDescription = NSPersistentStoreDescription()
|
|
||||||
storeDescription.type = NSInMemoryStoreType
|
|
||||||
persistentStoreDescriptions = [storeDescription]
|
|
||||||
} else {
|
|
||||||
super.init(name: "\(accountInfo!.id)_cache", managedObjectModel: MastodonCachePersistentStore.managedObjectModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
loadPersistentStores { (description, error) in
|
loadPersistentStores { (description, error) in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
fatalError("Unable to load persistent store: \(error)")
|
fatalError("Unable to load persistent store: \(error)")
|
||||||
|
@ -105,25 +96,18 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
private func upsert(account: Account, incrementReferenceCount: Bool) -> AccountMO {
|
private func upsert(account: Account) -> AccountMO {
|
||||||
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
|
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
|
||||||
accountMO.updateFrom(apiAccount: account, container: self)
|
accountMO.updateFrom(apiAccount: account, container: self)
|
||||||
if incrementReferenceCount {
|
|
||||||
accountMO.incrementReferenceCount()
|
|
||||||
}
|
|
||||||
return accountMO
|
return accountMO
|
||||||
} else {
|
} else {
|
||||||
let accountMO = AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
return AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
||||||
if incrementReferenceCount {
|
|
||||||
accountMO.incrementReferenceCount()
|
|
||||||
}
|
|
||||||
return accountMO
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOrUpdate(account: Account, incrementReferenceCount: Bool, completion: ((AccountMO) -> Void)? = nil) {
|
func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
let accountMO = self.upsert(account: account, incrementReferenceCount: incrementReferenceCount)
|
let accountMO = self.upsert(account: account)
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
|
@ -134,7 +118,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
|
|
||||||
func addAll(accounts: [Account], completion: (() -> Void)? = nil) {
|
func addAll(accounts: [Account], completion: (() -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
|
accounts.forEach { self.upsert(account: $0) }
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
|
@ -146,11 +130,9 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
func addAll(notifications: [Pachyderm.Notification], completion: (() -> Void)? = nil) {
|
func addAll(notifications: [Pachyderm.Notification], completion: (() -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
let statuses = notifications.compactMap { $0.status }
|
let statuses = notifications.compactMap { $0.status }
|
||||||
// filter out mentions, otherwise we would double increment the reference count of those accounts
|
let accounts = notifications.map { $0.account }
|
||||||
// since the status has the same account as the notification
|
|
||||||
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
|
|
||||||
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) }
|
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) }
|
||||||
accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
|
accounts.forEach { self.upsert(account: $0) }
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
|
@ -166,7 +148,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
var updatedStatuses = [String]()
|
var updatedStatuses = [String]()
|
||||||
|
|
||||||
block(self.backgroundContext, { (accounts) in
|
block(self.backgroundContext, { (accounts) in
|
||||||
accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
|
accounts.forEach { self.upsert(account: $0) }
|
||||||
updatedAccounts.append(contentsOf: accounts.map { $0.id })
|
updatedAccounts.append(contentsOf: accounts.map { $0.id })
|
||||||
}, { (statuses) in
|
}, { (statuses) in
|
||||||
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) }
|
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) }
|
||||||
|
|
|
@ -82,7 +82,6 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
|
||||||
public override func prepareForDeletion() {
|
public override func prepareForDeletion() {
|
||||||
super.prepareForDeletion()
|
super.prepareForDeletion()
|
||||||
reblog?.decrementReferenceCount()
|
reblog?.decrementReferenceCount()
|
||||||
account.decrementReferenceCount()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -93,7 +92,6 @@ extension StatusMO {
|
||||||
self.updateFrom(apiStatus: status, container: container)
|
self.updateFrom(apiStatus: status, container: container)
|
||||||
|
|
||||||
reblog?.incrementReferenceCount()
|
reblog?.incrementReferenceCount()
|
||||||
account.incrementReferenceCount()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFrom(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore) {
|
func updateFrom(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore) {
|
||||||
|
@ -108,7 +106,7 @@ extension StatusMO {
|
||||||
self.content = status.content
|
self.content = status.content
|
||||||
self.createdAt = status.createdAt
|
self.createdAt = status.createdAt
|
||||||
self.emojis = status.emojis
|
self.emojis = status.emojis
|
||||||
self.favourited = status.favourited ?? false
|
self.favourited = status.favourited
|
||||||
self.favouritesCount = status.favouritesCount
|
self.favouritesCount = status.favouritesCount
|
||||||
self.hashtags = status.hashtags
|
self.hashtags = status.hashtags
|
||||||
self.inReplyToAccountID = status.inReplyToAccountID
|
self.inReplyToAccountID = status.inReplyToAccountID
|
||||||
|
@ -117,7 +115,7 @@ extension StatusMO {
|
||||||
self.mentions = status.mentions
|
self.mentions = status.mentions
|
||||||
self.muted = status.muted ?? false
|
self.muted = status.muted ?? false
|
||||||
self.pinnedInternal = status.pinned ?? false
|
self.pinnedInternal = status.pinned ?? false
|
||||||
self.reblogged = status.reblogged ?? false
|
self.reblogged = status.reblogged
|
||||||
self.reblogsCount = status.reblogsCount
|
self.reblogsCount = status.reblogsCount
|
||||||
self.sensitive = status.sensitive
|
self.sensitive = status.sensitive
|
||||||
self.spoilerText = status.spoilerText
|
self.spoilerText = status.spoilerText
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
<attribute name="locked" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="locked" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="movedCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="movedCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="note" attributeType="String"/>
|
<attribute name="note" attributeType="String"/>
|
||||||
<attribute name="referenceCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
|
||||||
<attribute name="statusesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="statusesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="url" attributeType="URI"/>
|
<attribute name="url" attributeType="URI"/>
|
||||||
<attribute name="username" attributeType="String"/>
|
<attribute name="username" attributeType="String"/>
|
||||||
|
@ -50,6 +49,7 @@
|
||||||
<attribute name="uri" attributeType="String"/>
|
<attribute name="uri" attributeType="String"/>
|
||||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||||
<attribute name="visibilityString" attributeType="String"/>
|
<attribute name="visibilityString" attributeType="String"/>
|
||||||
|
|
||||||
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="Account"/>
|
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="Account"/>
|
||||||
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/>
|
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/>
|
||||||
<uniquenessConstraints>
|
<uniquenessConstraints>
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
</uniquenessConstraints>
|
</uniquenessConstraints>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="328"/>
|
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="313"/>
|
||||||
<element name="Status" positionX="-63" positionY="-18" width="128" height="418"/>
|
<element name="Status" positionX="-63" positionY="-18" width="128" height="418"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
|
@ -75,8 +75,6 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
||||||
let vc = AVPlayerViewController()
|
let vc = AVPlayerViewController()
|
||||||
vc.player = AVPlayer(url: $0.url)
|
vc.player = AVPlayer(url: $0.url)
|
||||||
return vc
|
return vc
|
||||||
case .gifv:
|
|
||||||
return GifvAttachmentViewController(attachment: $0)
|
|
||||||
default:
|
default:
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// GifvAttachmentViewController.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 5/12/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Pachyderm
|
|
||||||
import AVFoundation
|
|
||||||
|
|
||||||
class GifvAttachmentViewController: UIViewController {
|
|
||||||
|
|
||||||
private let attachment: Attachment
|
|
||||||
|
|
||||||
init(attachment: Attachment) {
|
|
||||||
precondition(attachment.kind == .gifv)
|
|
||||||
self.attachment = attachment
|
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func loadView() {
|
|
||||||
let asset = AVURLAsset(url: attachment.url)
|
|
||||||
self.view = GifvAttachmentView(asset: asset, gravity: .resizeAspect)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -10,27 +10,19 @@ import SwiftUI
|
||||||
|
|
||||||
struct LocalAccountAvatarView: View {
|
struct LocalAccountAvatarView: View {
|
||||||
let localAccountInfo: LocalData.UserAccountInfo
|
let localAccountInfo: LocalData.UserAccountInfo
|
||||||
@State var avatarImage: UIImage? = nil
|
@State
|
||||||
@ObservedObject var preferences = Preferences.shared
|
var avatarImage: UIImage? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let image: Image
|
let image: Image
|
||||||
if avatarImage == nil {
|
if avatarImage == nil {
|
||||||
let imageName: String
|
image = Image(systemName: "person.crop.square")
|
||||||
switch preferences.avatarStyle {
|
|
||||||
case .circle:
|
|
||||||
imageName = "person.crop.circle"
|
|
||||||
case .roundRect:
|
|
||||||
imageName = "person.crop.square"
|
|
||||||
}
|
|
||||||
image = Image(systemName: imageName).resizable()
|
|
||||||
} else {
|
} else {
|
||||||
image = Image(uiImage: self.avatarImage!).renderingMode(.original)
|
image = Image(uiImage: self.avatarImage!).renderingMode(.original)
|
||||||
}
|
}
|
||||||
return image
|
return image
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 30, height: 30)
|
.frame(width: 30, height: 30)
|
||||||
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 30)
|
|
||||||
.onAppear(perform: self.loadImage)
|
.onAppear(perform: self.loadImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,15 +61,6 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
fatalError("init(coder:) has not been implemeneted")
|
fatalError("init(coder:) has not been implemeneted")
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
|
||||||
if let id = accountID {
|
|
||||||
let container = mastodonController.persistentContainer
|
|
||||||
container.backgroundContext.perform {
|
|
||||||
container.account(for: id, in: container.backgroundContext)?.decrementReferenceCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
@ -99,7 +90,7 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true) { (_) in
|
self.mastodonController.persistentContainer.addOrUpdate(account: account) { (_) in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.updateAccountUI()
|
self.updateAccountUI()
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
|
|
|
@ -134,10 +134,11 @@ class SearchResultsViewController: EnhancedTableViewController {
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
|
|
||||||
self.mastodonController.persistentContainer.performBatchUpdates({ (context, addAccounts, addStatuses) in
|
self.mastodonController.persistentContainer.performBatchUpdates({ (context, addAccounts, addStatuses) in
|
||||||
oldSnapshot.itemIdentifiers(inSection: .accounts).forEach { (item) in
|
// todo: reference count accounts
|
||||||
guard case let .account(id) = item else { return }
|
// oldSnapshot.itemIdentifiers(inSection: .accounts).forEach { (item) in
|
||||||
self.mastodonController.persistentContainer.account(for: id, in: context)?.decrementReferenceCount()
|
// guard case let .account(id) = item else { return }
|
||||||
}
|
// self.mastodonController.persistentContainer.account(for: id, in: context)?.decrementReferenceCount()
|
||||||
|
// }
|
||||||
|
|
||||||
oldSnapshot.itemIdentifiers(inSection: .statuses).forEach { (item) in
|
oldSnapshot.itemIdentifiers(inSection: .statuses).forEach { (item) in
|
||||||
guard case let .status(id, _) = item else { return }
|
guard case let .status(id, _) = item else { return }
|
||||||
|
@ -157,6 +158,7 @@ class SearchResultsViewController: EnhancedTableViewController {
|
||||||
snapshot.appendSections([.statuses])
|
snapshot.appendSections([.statuses])
|
||||||
snapshot.appendItems(results.statuses.map { .status($0.id, .unknown) }, toSection: .statuses)
|
snapshot.appendItems(results.statuses.map { .status($0.id, .unknown) }, toSection: .statuses)
|
||||||
addStatuses(results.statuses)
|
addStatuses(results.statuses)
|
||||||
|
addAccounts(results.statuses.map { $0.account })
|
||||||
}
|
}
|
||||||
}, completion: {
|
}, completion: {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
|
@ -58,17 +58,6 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
|
||||||
if let accountIDs = self.accountIDs {
|
|
||||||
let container = self.mastodonController.persistentContainer
|
|
||||||
container.backgroundContext.perform {
|
|
||||||
for id in accountIDs {
|
|
||||||
container.account(for: id, in: container.backgroundContext)?.decrementReferenceCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
@ -82,11 +71,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
|
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
|
||||||
|
|
||||||
if let accountIDs = accountIDs {
|
if accountIDs == nil {
|
||||||
accountIDs.forEach { (id) in
|
|
||||||
self.mastodonController.persistentContainer.account(for: id)?.incrementReferenceCount()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// account IDs haven't been set, so perform a request to load them
|
// account IDs haven't been set, so perform a request to load them
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||||
fatalError("Missing cached status \(statusID)")
|
fatalError("Missing cached status \(statusID)")
|
||||||
|
|
|
@ -37,7 +37,7 @@ class InstanceTimelineViewController: TimelineTableViewController {
|
||||||
self.instanceURL = url
|
self.instanceURL = url
|
||||||
|
|
||||||
// the timeline VC only stores a weak reference to it, so we need to store a strong reference to make sure it's not released immediately
|
// the timeline VC only stores a weak reference to it, so we need to store a strong reference to make sure it's not released immediately
|
||||||
instanceMastodonController = MastodonController(instanceURL: url, transient: true)
|
instanceMastodonController = MastodonController(instanceURL: url)
|
||||||
|
|
||||||
super.init(for: .public(local: true), mastodonController: instanceMastodonController)
|
super.init(for: .public(local: true), mastodonController: instanceMastodonController)
|
||||||
|
|
||||||
|
|
|
@ -210,7 +210,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
let bookmarked = status.bookmarked ?? false
|
let bookmarked = status.bookmarked ?? false
|
||||||
customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0)
|
customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0)
|
||||||
|
|
||||||
if apiController.account != nil, status.account.id == apiController.account.id {
|
if status.account.id == apiController.account.id {
|
||||||
let pinned = status.pinned ?? false
|
let pinned = status.pinned ?? false
|
||||||
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
|
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,8 +81,6 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
loadVideo()
|
loadVideo()
|
||||||
case .audio:
|
case .audio:
|
||||||
loadAudio()
|
loadAudio()
|
||||||
case .gifv:
|
|
||||||
loadGifv()
|
|
||||||
default:
|
default:
|
||||||
preconditionFailure("invalid attachment type")
|
preconditionFailure("invalid attachment type")
|
||||||
}
|
}
|
||||||
|
@ -113,9 +111,9 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
let asset = AVURLAsset(url: attachmentURL)
|
let asset = AVURLAsset(url: attachmentURL)
|
||||||
let generator = AVAssetImageGenerator(asset: asset)
|
let generator = AVAssetImageGenerator(asset: asset)
|
||||||
generator.appliesPreferredTrackTransform = true
|
generator.appliesPreferredTrackTransform = true
|
||||||
guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return }
|
guard let image = try? generator.copyCGImage(at: CMTime(seconds: 0, preferredTimescale: 1), actualTime: nil) else { return }
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async {
|
||||||
guard let self = self, self.attachment.url == attachmentURL else { return }
|
guard self.attachment.url == attachmentURL else { return }
|
||||||
self.image = UIImage(cgImage: image)
|
self.image = UIImage(cgImage: image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,30 +150,6 @@ class AttachmentView: UIImageView, GIFAnimatable {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGifv() {
|
|
||||||
let attachmentURL = self.attachment.url
|
|
||||||
let asset = AVURLAsset(url: attachmentURL)
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
|
||||||
let generator = AVAssetImageGenerator(asset: asset)
|
|
||||||
generator.appliesPreferredTrackTransform = true
|
|
||||||
guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return }
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
|
||||||
guard let self = self, self.attachment.url == attachmentURL else { return }
|
|
||||||
self.image = UIImage(cgImage: image)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let gifvView = GifvAttachmentView(asset: asset, gravity: .resizeAspectFill)
|
|
||||||
gifvView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(gifvView)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
gifvView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
||||||
gifvView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
gifvView.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
gifvView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
override func display(_ layer: CALayer) {
|
override func display(_ layer: CALayer) {
|
||||||
updateImageIfNeeded()
|
updateImageIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
||||||
|
|
||||||
class AttachmentsContainerView: UIView {
|
class AttachmentsContainerView: UIView {
|
||||||
|
|
||||||
static let supportedAttachmentTypes = [Attachment.Kind.image, .video, .audio, .gifv]
|
static let supportedAttachmentTypes = [Attachment.Kind.image, .video, .audio]
|
||||||
|
|
||||||
weak var delegate: AttachmentViewDelegate?
|
weak var delegate: AttachmentViewDelegate?
|
||||||
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
//
|
|
||||||
// GifvAttachmentView.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 5/12/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import AVFoundation
|
|
||||||
|
|
||||||
class GifvAttachmentView: UIView {
|
|
||||||
|
|
||||||
override class var layerClass: AnyClass {
|
|
||||||
return AVPlayerLayer.self
|
|
||||||
}
|
|
||||||
|
|
||||||
private var playerLayer: AVPlayerLayer {
|
|
||||||
layer as! AVPlayerLayer
|
|
||||||
}
|
|
||||||
|
|
||||||
private let item: AVPlayerItem
|
|
||||||
private let player: AVPlayer
|
|
||||||
|
|
||||||
init(asset: AVAsset, gravity: AVLayerVideoGravity) {
|
|
||||||
item = AVPlayerItem(asset: asset)
|
|
||||||
player = AVPlayer(playerItem: item)
|
|
||||||
|
|
||||||
super.init(frame: .zero)
|
|
||||||
|
|
||||||
playerLayer.player = player
|
|
||||||
playerLayer.videoGravity = gravity
|
|
||||||
player.play()
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(restartItem), name: .AVPlayerItemDidPlayToEndTime, object: item)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func restartItem() {
|
|
||||||
item.seek(to: .zero) { (success) in
|
|
||||||
guard success else { return }
|
|
||||||
self.player.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -261,7 +261,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
mastodonController.run(request) { response in
|
mastodonController.run(request) { response in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if case let .success(newStatus, _) = response {
|
if case let .success(newStatus, _) = response {
|
||||||
self.favorited = newStatus.favourited ?? false
|
self.favorited = newStatus.favourited
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
} else {
|
} else {
|
||||||
|
@ -286,7 +286,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
mastodonController.run(request) { response in
|
mastodonController.run(request) { response in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if case let .success(newStatus, _) = response {
|
if case let .success(newStatus, _) = response {
|
||||||
self.reblogged = newStatus.reblogged ?? false
|
self.reblogged = newStatus.reblogged
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue