Compare commits
No commits in common. "56a0518c80acb44b30793b3ec6f38103d89a6899" and "514e569bd5ab72fe5034f0f9f4dd712535e83fc3" have entirely different histories.
56a0518c80
...
514e569bd5
|
@ -34,13 +34,6 @@ public class Hashtag: Codable {
|
||||||
self.history = try container.decodeIfPresent([History].self, forKey: .history)
|
self.history = try container.decodeIfPresent([History].self, forKey: .history)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
||||||
try container.encode(name, forKey: .name)
|
|
||||||
try container.encode(url.absoluteString, forKey: .url)
|
|
||||||
try container.encodeIfPresent(history, forKey: .history)
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case name
|
case name
|
||||||
case url
|
case url
|
||||||
|
|
|
@ -236,7 +236,6 @@
|
||||||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B81F432560390300F6E31D /* MenuController.swift */; };
|
D6B81F442560390300F6E31D /* MenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B81F432560390300F6E31D /* MenuController.swift */; };
|
||||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */; };
|
||||||
D6B93667281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */; };
|
D6B93667281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */; };
|
||||||
D6B9366B281EE77E00237D0E /* PollVoteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366A281EE77E00237D0E /* PollVoteButton.swift */; };
|
|
||||||
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */; };
|
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */; };
|
||||||
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */; };
|
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */; };
|
||||||
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */; };
|
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */; };
|
||||||
|
@ -282,7 +281,7 @@
|
||||||
D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AA265AAD6B00C4AA01 /* Media.xcassets */; };
|
D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AA265AAD6B00C4AA01 /* Media.xcassets */; };
|
||||||
D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
|
D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
|
||||||
D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
|
D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
|
||||||
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
D6E343BA265AAD8C00C4AA01 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = D6E343B9265AAD8C00C4AA01 /* Action.js */; };
|
D6E343BA265AAD8C00C4AA01 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = D6E343B9265AAD8C00C4AA01 /* Action.js */; };
|
||||||
D6E4267725327FB400C02E1C /* ComposeAutocompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */; };
|
D6E4267725327FB400C02E1C /* ComposeAutocompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */; };
|
||||||
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
|
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
|
||||||
|
@ -578,7 +577,6 @@
|
||||||
D6B81F432560390300F6E31D /* MenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuController.swift; sourceTree = "<group>"; };
|
D6B81F432560390300F6E31D /* MenuController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuController.swift; sourceTree = "<group>"; };
|
||||||
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
D6B8DB332182A59300424AF7 /* UIAlertController+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Visibility.swift"; sourceTree = "<group>"; };
|
||||||
D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarMyProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6B93666281D937300237D0E /* MainSidebarMyProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarMyProfileCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6B9366A281EE77E00237D0E /* PollVoteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollVoteButton.swift; sourceTree = "<group>"; };
|
|
||||||
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedTableViewController.swift; sourceTree = "<group>"; };
|
D6BC8747219738E1006163F1 /* EnhancedTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedTableViewController.swift; sourceTree = "<group>"; };
|
||||||
D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPageViewController.swift; sourceTree = "<group>"; };
|
D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPageViewController.swift; sourceTree = "<group>"; };
|
||||||
D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellnessPrefsView.swift; sourceTree = "<group>"; };
|
D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellnessPrefsView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -751,7 +749,6 @@
|
||||||
D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */,
|
D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */,
|
||||||
D623A5402635FB3C0095BD04 /* PollOptionView.swift */,
|
D623A5402635FB3C0095BD04 /* PollOptionView.swift */,
|
||||||
D623A542263634100095BD04 /* PollOptionCheckboxView.swift */,
|
D623A542263634100095BD04 /* PollOptionCheckboxView.swift */,
|
||||||
D6B9366A281EE77E00237D0E /* PollVoteButton.swift */,
|
|
||||||
);
|
);
|
||||||
path = Poll;
|
path = Poll;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1933,7 +1930,6 @@
|
||||||
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */,
|
D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */,
|
||||||
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */,
|
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */,
|
||||||
D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */,
|
D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */,
|
||||||
D6B9366B281EE77E00237D0E /* PollVoteButton.swift in Sources */,
|
|
||||||
D6BC9DB5232D4CE3002CA326 /* NotificationsMode.swift in Sources */,
|
D6BC9DB5232D4CE3002CA326 /* NotificationsMode.swift in Sources */,
|
||||||
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */,
|
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */,
|
||||||
D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */,
|
D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */,
|
||||||
|
@ -2022,7 +2018,6 @@
|
||||||
};
|
};
|
||||||
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */ = {
|
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
platformFilter = ios;
|
|
||||||
target = D6E343A7265AAD6B00C4AA01 /* OpenInTusker */;
|
target = D6E343A7265AAD6B00C4AA01 /* OpenInTusker */;
|
||||||
targetProxy = D6E343B2265AAD6B00C4AA01 /* PBXContainerItemProxy */;
|
targetProxy = D6E343B2265AAD6B00C4AA01 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,7 +29,7 @@ class BookmarkStatusActivity: StatusActivity {
|
||||||
let request = Status.bookmark(status.id)
|
let request = Status.bookmark(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
} else {
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
|
|
@ -28,7 +28,7 @@ class MuteConversationActivity: StatusActivity {
|
||||||
let request = Status.muteConversation(status.id)
|
let request = Status.muteConversation(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
} else {
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
|
|
@ -28,7 +28,7 @@ class PinStatusActivity: StatusActivity {
|
||||||
let request = Status.pin(status.id)
|
let request = Status.pin(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
} else {
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
|
|
@ -29,7 +29,7 @@ class UnbookmarkStatusActivity: StatusActivity {
|
||||||
let request = Status.unbookmark(status.id)
|
let request = Status.unbookmark(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
} else {
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
|
|
@ -28,7 +28,7 @@ class UnmuteConversationActivity: StatusActivity {
|
||||||
let request = Status.unmuteConversation(status.id)
|
let request = Status.unmuteConversation(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
} else {
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
|
|
@ -28,7 +28,7 @@ class UnpinStatusActivity: StatusActivity {
|
||||||
let request = Status.unpin(status.id)
|
let request = Status.unpin(status.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status)
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
} else {
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
|
|
@ -137,7 +137,7 @@ class MastodonController: ObservableObject {
|
||||||
} else {
|
} else {
|
||||||
// the first time the user's account is added to the store,
|
// the first time the user's account is added to the store,
|
||||||
// increment its reference count so that it's never removed
|
// increment its reference count so that it's never removed
|
||||||
self.persistentContainer.addOrUpdate(account: account)
|
self.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true)
|
||||||
}
|
}
|
||||||
completion?(.success(account))
|
completion?(.success(account))
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,10 +32,10 @@ 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
|
||||||
@NSManaged public var lastFetchedAt: Date?
|
|
||||||
@NSManaged public var movedTo: AccountMO?
|
@NSManaged public var movedTo: AccountMO?
|
||||||
|
|
||||||
@LazilyDecoding(arrayFrom: \AccountMO.emojisData)
|
@LazilyDecoding(arrayFrom: \AccountMO.emojisData)
|
||||||
|
@ -47,20 +47,30 @@ 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 }
|
||||||
|
|
||||||
public override func awakeFromFetch() {
|
func incrementReferenceCount() {
|
||||||
super.awakeFromFetch()
|
referenceCount += 1
|
||||||
|
}
|
||||||
|
|
||||||
managedObjectContext?.perform {
|
func decrementReferenceCount() {
|
||||||
self.lastFetchedAt = Date()
|
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) {
|
||||||
|
|
|
@ -65,19 +65,26 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
private func upsert(status: Status, context: NSManagedObjectContext) -> StatusMO {
|
private func upsert(status: Status, incrementReferenceCount: Bool, context: NSManagedObjectContext) -> StatusMO {
|
||||||
if let statusMO = self.status(for: status.id, in: context) {
|
if let statusMO = self.status(for: status.id, in: context) {
|
||||||
statusMO.updateFrom(apiStatus: status, container: self)
|
statusMO.updateFrom(apiStatus: status, container: self)
|
||||||
|
if incrementReferenceCount {
|
||||||
|
statusMO.incrementReferenceCount()
|
||||||
|
}
|
||||||
return statusMO
|
return statusMO
|
||||||
} else {
|
} else {
|
||||||
return StatusMO(apiStatus: status, container: self, context: context)
|
let statusMO = StatusMO(apiStatus: status, container: self, context: context)
|
||||||
|
if incrementReferenceCount {
|
||||||
|
statusMO.incrementReferenceCount()
|
||||||
|
}
|
||||||
|
return statusMO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOrUpdate(status: Status, context: NSManagedObjectContext? = nil, completion: ((StatusMO) -> Void)? = nil) {
|
func addOrUpdate(status: Status, incrementReferenceCount: Bool, context: NSManagedObjectContext? = nil, completion: ((StatusMO) -> Void)? = nil) {
|
||||||
let context = context ?? backgroundContext
|
let context = context ?? backgroundContext
|
||||||
context.perform {
|
context.perform {
|
||||||
let statusMO = self.upsert(status: status, context: context)
|
let statusMO = self.upsert(status: status, incrementReferenceCount: incrementReferenceCount, context: context)
|
||||||
if context.hasChanges {
|
if context.hasChanges {
|
||||||
try! context.save()
|
try! context.save()
|
||||||
}
|
}
|
||||||
|
@ -88,7 +95,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
|
|
||||||
func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
|
func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) }
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
|
@ -110,18 +117,25 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
private func upsert(account: Account) -> AccountMO {
|
private func upsert(account: Account, incrementReferenceCount: Bool) -> 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 {
|
||||||
return AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
let accountMO = AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
||||||
|
if incrementReferenceCount {
|
||||||
|
accountMO.incrementReferenceCount()
|
||||||
|
}
|
||||||
|
return accountMO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
|
func addOrUpdate(account: Account, incrementReferenceCount: Bool, completion: ((AccountMO) -> Void)? = nil) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
let accountMO = self.upsert(account: account)
|
let accountMO = self.upsert(account: account, incrementReferenceCount: incrementReferenceCount)
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
|
@ -166,7 +180,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) }
|
accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
|
@ -181,8 +195,8 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
// filter out mentions, otherwise we would double increment the reference count of those accounts
|
// filter out mentions, otherwise we would double increment the reference count of those accounts
|
||||||
// since the status has the same account as the notification
|
// since the status has the same account as the notification
|
||||||
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
|
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
|
||||||
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) }
|
||||||
accounts.forEach { self.upsert(account: $0) }
|
accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
|
@ -198,10 +212,10 @@ 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) }
|
accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) }
|
||||||
updatedAccounts.append(contentsOf: accounts.map { $0.id })
|
updatedAccounts.append(contentsOf: accounts.map { $0.id })
|
||||||
}, { (statuses) in
|
}, { (statuses) in
|
||||||
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
|
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) }
|
||||||
updatedStatuses.append(contentsOf: statuses.map { $0.id })
|
updatedStatuses.append(contentsOf: statuses.map { $0.id })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
|
||||||
@NSManaged private var pinnedInternal: Bool
|
@NSManaged private var pinnedInternal: Bool
|
||||||
@NSManaged public var reblogged: Bool
|
@NSManaged public var reblogged: Bool
|
||||||
@NSManaged public var reblogsCount: Int
|
@NSManaged public var reblogsCount: Int
|
||||||
|
@NSManaged public var referenceCount: Int
|
||||||
@NSManaged public var sensitive: Bool
|
@NSManaged public var sensitive: Bool
|
||||||
@NSManaged public var spoilerText: String
|
@NSManaged public var spoilerText: String
|
||||||
@NSManaged public var uri: String // todo: are both uri and url necessary?
|
@NSManaged public var uri: String // todo: are both uri and url necessary?
|
||||||
|
@ -45,7 +46,6 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
|
||||||
@NSManaged public var account: AccountMO
|
@NSManaged public var account: AccountMO
|
||||||
@NSManaged public var reblog: StatusMO?
|
@NSManaged public var reblog: StatusMO?
|
||||||
@NSManaged public var localOnly: Bool
|
@NSManaged public var localOnly: Bool
|
||||||
@NSManaged public var lastFetchedAt: Date?
|
|
||||||
|
|
||||||
@LazilyDecoding(arrayFrom: \StatusMO.attachmentsData)
|
@LazilyDecoding(arrayFrom: \StatusMO.attachmentsData)
|
||||||
public var attachments: [Attachment]
|
public var attachments: [Attachment]
|
||||||
|
@ -77,20 +77,32 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func awakeFromFetch() {
|
func incrementReferenceCount() {
|
||||||
super.awakeFromFetch()
|
referenceCount += 1
|
||||||
|
}
|
||||||
|
|
||||||
managedObjectContext?.perform {
|
func decrementReferenceCount() {
|
||||||
self.lastFetchedAt = Date()
|
referenceCount -= 1
|
||||||
|
if referenceCount <= 0 {
|
||||||
|
managedObjectContext!.delete(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override func prepareForDeletion() {
|
||||||
|
super.prepareForDeletion()
|
||||||
|
reblog?.decrementReferenceCount()
|
||||||
|
account.decrementReferenceCount()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusMO {
|
extension StatusMO {
|
||||||
convenience init(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore, context: NSManagedObjectContext) {
|
convenience init(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore, context: NSManagedObjectContext) {
|
||||||
self.init(context: context)
|
self.init(context: context)
|
||||||
self.updateFrom(apiStatus: status, container: container)
|
self.updateFrom(apiStatus: status, container: container)
|
||||||
|
|
||||||
|
reblog?.incrementReferenceCount()
|
||||||
|
account.incrementReferenceCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFrom(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore) {
|
func updateFrom(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21E230" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21D49" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="Account" representedClassName="AccountMO" syncable="YES">
|
<entity name="Account" representedClassName="AccountMO" syncable="YES">
|
||||||
<attribute name="acct" attributeType="String"/>
|
<attribute name="acct" attributeType="String"/>
|
||||||
<attribute name="avatar" optional="YES" attributeType="URI"/>
|
<attribute name="avatar" optional="YES" attributeType="URI"/>
|
||||||
|
@ -12,10 +12,10 @@
|
||||||
<attribute name="followingCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="followingCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="header" optional="YES" attributeType="URI"/>
|
<attribute name="header" optional="YES" attributeType="URI"/>
|
||||||
<attribute name="id" attributeType="String"/>
|
<attribute name="id" attributeType="String"/>
|
||||||
<attribute name="lastFetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
|
||||||
<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"/>
|
||||||
|
@ -54,7 +54,6 @@
|
||||||
<attribute name="id" attributeType="String"/>
|
<attribute name="id" attributeType="String"/>
|
||||||
<attribute name="inReplyToAccountID" optional="YES" attributeType="String"/>
|
<attribute name="inReplyToAccountID" optional="YES" attributeType="String"/>
|
||||||
<attribute name="inReplyToID" optional="YES" attributeType="String"/>
|
<attribute name="inReplyToID" optional="YES" attributeType="String"/>
|
||||||
<attribute name="lastFetchedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
|
||||||
<attribute name="localOnly" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="localOnly" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="mentionsData" attributeType="Binary"/>
|
<attribute name="mentionsData" attributeType="Binary"/>
|
||||||
<attribute name="muted" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="muted" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
@ -62,6 +61,7 @@
|
||||||
<attribute name="pollData" optional="YES" attributeType="Binary"/>
|
<attribute name="pollData" optional="YES" attributeType="Binary"/>
|
||||||
<attribute name="reblogged" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="reblogged" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="reblogsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="reblogsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="referenceCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="spoilerText" attributeType="String"/>
|
<attribute name="spoilerText" attributeType="String"/>
|
||||||
<attribute name="uri" attributeType="String"/>
|
<attribute name="uri" attributeType="String"/>
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
</uniquenessConstraints>
|
</uniquenessConstraints>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="343"/>
|
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="329"/>
|
||||||
<element name="Relationship" positionX="63" positionY="135" width="128" height="208"/>
|
<element name="Relationship" positionX="63" positionY="135" width="128" height="208"/>
|
||||||
<element name="Status" positionX="-63" positionY="-18" width="128" height="449"/>
|
<element name="Status" positionX="-63" positionY="-18" width="128" height="449"/>
|
||||||
</elements>
|
</elements>
|
||||||
|
|
|
@ -10,7 +10,6 @@ import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import CrashReporter
|
import CrashReporter
|
||||||
import MessageUI
|
import MessageUI
|
||||||
import CoreData
|
|
||||||
|
|
||||||
class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
|
@ -121,22 +120,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
rootVC.sceneDidEnterBackground()
|
rootVC.sceneDidEnterBackground()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let context = scene.session.mastodonController?.persistentContainer.viewContext {
|
try? scene.session.mastodonController?.persistentContainer.viewContext.save()
|
||||||
var minDate = Date()
|
|
||||||
minDate.addTimeInterval(-7 * 24 * 60 * 60)
|
|
||||||
|
|
||||||
let statusReq: NSFetchRequest<NSFetchRequestResult> = StatusMO.fetchRequest()
|
|
||||||
statusReq.predicate = NSPredicate(format: "(lastFetchedAt = nil) OR (lastFetchedAt < %@)", minDate as NSDate)
|
|
||||||
let deleteStatusReq = NSBatchDeleteRequest(fetchRequest: statusReq)
|
|
||||||
_ = try? context.execute(deleteStatusReq)
|
|
||||||
|
|
||||||
let accountReq: NSFetchRequest<NSFetchRequestResult> = AccountMO.fetchRequest()
|
|
||||||
accountReq.predicate = NSPredicate(format: "(lastFetchedAt = nil) OR (lastFetchedAt < %@)", minDate as NSDate)
|
|
||||||
let deleteAccountReq = NSBatchDeleteRequest(fetchRequest: accountReq)
|
|
||||||
_ = try? context.execute(deleteAccountReq)
|
|
||||||
|
|
||||||
try? context.save()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handlePendingCrashReport(_ report: PLCrashReport, session: UISceneSession) {
|
private func handlePendingCrashReport(_ report: PLCrashReport, session: UISceneSession) {
|
||||||
|
|
|
@ -67,9 +67,3 @@ class AccountListTableViewController: EnhancedTableViewController {
|
||||||
extension AccountListTableViewController: TuskerNavigationDelegate {
|
extension AccountListTableViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AccountListTableViewController: ToastableViewController {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AccountListTableViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
let request = Status.unbookmark(status.id)
|
let request = Status.unbookmark(status.id)
|
||||||
self.mastodonController.run(request) { (response) in
|
self.mastodonController.run(request) { (response) in
|
||||||
guard case let .success(newStatus, _) = response else { fatalError() }
|
guard case let .success(newStatus, _) = response else { fatalError() }
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
||||||
self.statuses.remove(at: indexPath.row)
|
self.statuses.remove(at: indexPath.row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,15 +153,6 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BookmarksTableViewController: TuskerNavigationDelegate {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension BookmarksTableViewController: ToastableViewController {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension BookmarksTableViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension BookmarksTableViewController: StatusTableViewCellDelegate {
|
extension BookmarksTableViewController: StatusTableViewCellDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,14 @@ class ConversationTableViewController: EnhancedTableViewController {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
guard let persistentContainer = mastodonController?.persistentContainer else { return }
|
||||||
|
let snapshot = dataSource.snapshot()
|
||||||
|
for case let .status(id: id, state: _) in snapshot.itemIdentifiers {
|
||||||
|
persistentContainer.status(for: id)?.decrementReferenceCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
@ -141,7 +149,7 @@ class ConversationTableViewController: EnhancedTableViewController {
|
||||||
switch response {
|
switch response {
|
||||||
case let .success(status, _):
|
case let .success(status, _):
|
||||||
let viewContext = self.mastodonController.persistentContainer.viewContext
|
let viewContext = self.mastodonController.persistentContainer.viewContext
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status, context: viewContext) { (statusMO) in
|
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false, context: viewContext) { (statusMO) in
|
||||||
self.mainStatusLoaded(statusMO)
|
self.mainStatusLoaded(statusMO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +169,8 @@ class ConversationTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func mainStatusLoaded(_ mainStatus: StatusMO) {
|
private func mainStatusLoaded(_ mainStatus: StatusMO) {
|
||||||
|
mainStatus.incrementReferenceCount()
|
||||||
|
|
||||||
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState)
|
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState)
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
|
@ -444,9 +454,6 @@ extension ConversationTableViewController: TuskerNavigationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConversationTableViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ConversationTableViewController: StatusTableViewCellDelegate {
|
extension ConversationTableViewController: StatusTableViewCellDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||||
|
|
|
@ -134,10 +134,8 @@ extension ProfileDirectoryViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileDirectoryViewController: ToastableViewController {
|
extension ProfileDirectoryViewController: MenuPreviewProvider {
|
||||||
}
|
var navigationDelegate: TuskerNavigationDelegate? { self }
|
||||||
|
|
||||||
extension ProfileDirectoryViewController: MenuActionProvider {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileDirectoryViewController: UICollectionViewDelegate {
|
extension ProfileDirectoryViewController: UICollectionViewDelegate {
|
||||||
|
|
|
@ -110,8 +110,6 @@ extension TrendingHashtagsViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TrendingHashtagsViewController: ToastableViewController {
|
extension TrendingHashtagsViewController: MenuPreviewProvider {
|
||||||
}
|
var navigationDelegate: TuskerNavigationDelegate? { self }
|
||||||
|
|
||||||
extension TrendingHashtagsViewController: MenuActionProvider {
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,8 +104,6 @@ extension TrendingLinksViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TrendingLinksViewController: ToastableViewController {
|
extension TrendingLinksViewController: MenuPreviewProvider {
|
||||||
}
|
var navigationDelegate: TuskerNavigationDelegate? { self }
|
||||||
|
|
||||||
extension TrendingLinksViewController: MenuActionProvider {
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,12 +85,6 @@ extension TrendingStatusesViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TrendingStatusesViewController: ToastableViewController {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TrendingStatusesViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TrendingStatusesViewController: StatusTableViewCellDelegate {
|
extension TrendingStatusesViewController: StatusTableViewCellDelegate {
|
||||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||||
tableView.beginUpdates()
|
tableView.beginUpdates()
|
||||||
|
|
|
@ -176,9 +176,3 @@ extension EditListAccountsViewController: SearchResultsViewControllerDelegate {
|
||||||
extension EditListAccountsViewController: TuskerNavigationDelegate {
|
extension EditListAccountsViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EditListAccountsViewController: ToastableViewController {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EditListAccountsViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
|
@ -250,9 +250,6 @@ extension NotificationsTableViewController: TuskerNavigationDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationsTableViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NotificationsTableViewController: StatusTableViewCellDelegate {
|
extension NotificationsTableViewController: StatusTableViewCellDelegate {
|
||||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||||
cellHeightChanged()
|
cellHeightChanged()
|
||||||
|
|
|
@ -57,6 +57,12 @@ class ProfileViewController: UIPageViewController {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if let accountID = accountID {
|
||||||
|
mastodonController.persistentContainer.account(for: accountID)?.decrementReferenceCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
@ -116,7 +122,7 @@ class ProfileViewController: UIPageViewController {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
switch response {
|
switch response {
|
||||||
case .success(let account, _):
|
case .success(let account, _):
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(account: account) { (account) in
|
self.mastodonController.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true) { (account) in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.updateAccountUI()
|
self.updateAccountUI()
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,9 +172,24 @@ class SearchResultsViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showSearchResults(_ results: SearchResults) {
|
private func showSearchResults(_ results: SearchResults) {
|
||||||
|
let oldSnapshot = self.dataSource.snapshot()
|
||||||
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
|
||||||
|
if oldSnapshot.indexOfSection(.accounts) != nil {
|
||||||
|
oldSnapshot.itemIdentifiers(inSection: .accounts).forEach { (item) in
|
||||||
|
guard case let .account(id) = item else { return }
|
||||||
|
self.mastodonController.persistentContainer.account(for: id, in: context)?.decrementReferenceCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldSnapshot.indexOfSection(.statuses) != nil {
|
||||||
|
oldSnapshot.itemIdentifiers(inSection: .statuses).forEach { (item) in
|
||||||
|
guard case let .status(id, _) = item else { return }
|
||||||
|
self.mastodonController.persistentContainer.status(for: id, in: context)?.decrementReferenceCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let resultTypes = self.resultTypes
|
let resultTypes = self.resultTypes
|
||||||
if !results.accounts.isEmpty && (resultTypes == nil || resultTypes!.contains(.accounts)) {
|
if !results.accounts.isEmpty && (resultTypes == nil || resultTypes!.contains(.accounts)) {
|
||||||
snapshot.appendSections([.accounts])
|
snapshot.appendSections([.accounts])
|
||||||
|
@ -288,15 +303,6 @@ extension SearchResultsViewController: UISearchBarDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SearchResultsViewController: TuskerNavigationDelegate {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SearchResultsViewController: ToastableViewController {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SearchResultsViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SearchResultsViewController: StatusTableViewCellDelegate {
|
extension SearchResultsViewController: StatusTableViewCellDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||||
|
|
|
@ -60,6 +60,16 @@ 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 = 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()
|
||||||
|
|
||||||
|
@ -73,7 +83,11 @@ 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 accountIDs == nil {
|
if let accountIDs = accountIDs {
|
||||||
|
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)")
|
||||||
|
@ -144,15 +158,6 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusActionAccountListTableViewController: TuskerNavigationDelegate {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusActionAccountListTableViewController: ToastableViewController {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusActionAccountListTableViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusActionAccountListTableViewController: StatusTableViewCellDelegate {
|
extension StatusActionAccountListTableViewController: StatusTableViewCellDelegate {
|
||||||
var apiController: MastodonController { mastodonController }
|
var apiController: MastodonController { mastodonController }
|
||||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
|
||||||
|
|
|
@ -42,6 +42,18 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
guard let persistentContainer = mastodonController?.persistentContainer,
|
||||||
|
let dataSource = dataSource else { return }
|
||||||
|
// decrement reference counts of any statuses we still have
|
||||||
|
// if the app is currently being quit, this will not affect the persisted data because
|
||||||
|
// the persistent container would already have been saved in SceneDelegate.sceneDidEnterBackground(_:)
|
||||||
|
// todo: remove the whole reference count system
|
||||||
|
for case let .status(id: id, state: _) in dataSource.snapshot().itemIdentifiers {
|
||||||
|
persistentContainer.status(for: id)?.decrementReferenceCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
@ -233,6 +245,12 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func willRemoveItems(_ items: [Item]) {
|
||||||
|
for case let .status(id: id, state: _) in items {
|
||||||
|
mastodonController.persistentContainer.status(for: id)?.decrementReferenceCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
super.scrollViewWillBeginDragging(scrollView)
|
super.scrollViewWillBeginDragging(scrollView)
|
||||||
|
|
||||||
|
@ -294,9 +312,6 @@ extension TimelineTableViewController: StatusTableViewCellDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineTableViewController: MenuActionProvider {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TimelineTableViewController: UITableViewDataSourcePrefetching, StatusTablePrefetching {
|
extension TimelineTableViewController: UITableViewDataSourcePrefetching, StatusTablePrefetching {
|
||||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||||
let ids: [String] = indexPaths.compactMap {
|
let ids: [String] = indexPaths.compactMap {
|
||||||
|
|
|
@ -10,33 +10,29 @@ import UIKit
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
protocol MenuActionProvider: AnyObject {
|
|
||||||
var navigationDelegate: TuskerNavigationDelegate? { get }
|
|
||||||
var toastableViewController: ToastableViewController? { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol MenuPreviewProvider: AnyObject {
|
protocol MenuPreviewProvider: AnyObject {
|
||||||
|
|
||||||
typealias PreviewProviders = (content: UIContextMenuContentPreviewProvider, actions: () -> [UIMenuElement])
|
typealias PreviewProviders = (content: UIContextMenuContentPreviewProvider, actions: () -> [UIMenuElement])
|
||||||
|
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { get }
|
||||||
|
|
||||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders?
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol CustomPreviewPresenting {
|
protocol CustomPreviewPresenting {
|
||||||
func presentFromPreview(presenter: UIViewController)
|
func presentFromPreview(presenter: UIViewController)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MenuActionProvider where Self: TuskerNavigationDelegate {
|
extension MenuPreviewProvider {
|
||||||
var navigationDelegate: TuskerNavigationDelegate? { self }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MenuActionProvider where Self: ToastableViewController {
|
|
||||||
var toastableViewController: ToastableViewController? { self }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MenuActionProvider {
|
|
||||||
|
|
||||||
private var mastodonController: MastodonController? { navigationDelegate?.apiController }
|
private var mastodonController: MastodonController? { navigationDelegate?.apiController }
|
||||||
|
|
||||||
|
// Default no-op implementation
|
||||||
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func actionsForProfile(accountID: String, sourceView: UIView?) -> [UIMenuElement] {
|
func actionsForProfile(accountID: String, sourceView: UIView?) -> [UIMenuElement] {
|
||||||
guard let mastodonController = mastodonController,
|
guard let mastodonController = mastodonController,
|
||||||
let account = mastodonController.persistentContainer.account(for: accountID) else { return [] }
|
let account = mastodonController.persistentContainer.account(for: accountID) else { return [] }
|
||||||
|
@ -88,7 +84,7 @@ extension MenuActionProvider {
|
||||||
|
|
||||||
func actionsForURL(_ url: URL, sourceView: UIView?) -> [UIAction] {
|
func actionsForURL(_ url: URL, sourceView: UIView?) -> [UIAction] {
|
||||||
return [
|
return [
|
||||||
openInSafariAction(url: url),
|
openInSafariAction(url: url),
|
||||||
createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in
|
createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.navigationDelegate?.showMoreOptions(forURL: url, sourceView: sourceView)
|
self.navigationDelegate?.showMoreOptions(forURL: url, sourceView: sourceView)
|
||||||
|
@ -139,15 +135,8 @@ extension MenuActionProvider {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let request = (bookmarked ? Status.unbookmark : Status.bookmark)(status.id)
|
let request = (bookmarked ? Status.unbookmark : Status.bookmark)(status.id)
|
||||||
self.mastodonController?.run(request) { (response) in
|
self.mastodonController?.run(request) { (response) in
|
||||||
switch response {
|
if case let .success(status, _) = response {
|
||||||
case .success(let status, _):
|
self.mastodonController?.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
self.mastodonController?.persistentContainer.addOrUpdate(status: status)
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
if let toastable = self.toastableViewController {
|
|
||||||
let config = ToastConfiguration(from: error, with: "Error \(bookmarked ? "Unb" : "B")ookmarking", in: toastable, retryAction: nil)
|
|
||||||
toastable.showToast(configuration: config, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -168,14 +157,8 @@ extension MenuActionProvider {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let request = (muted ? Status.unmuteConversation : Status.muteConversation)(status.id)
|
let request = (muted ? Status.unmuteConversation : Status.muteConversation)(status.id)
|
||||||
self.mastodonController?.run(request) { (response) in
|
self.mastodonController?.run(request) { (response) in
|
||||||
switch response {
|
if case let .success(status, _) = response {
|
||||||
case .success(let status, _):
|
self.mastodonController?.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
self.mastodonController?.persistentContainer.addOrUpdate(status: status)
|
|
||||||
case .failure(let error):
|
|
||||||
if let toastable = self.toastableViewController {
|
|
||||||
let config = ToastConfiguration(from: error, with: "Error \(muted ? "Unm" : "M")uting", in: toastable, retryAction: nil)
|
|
||||||
toastable.showToast(configuration: config, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
@ -190,14 +173,8 @@ extension MenuActionProvider {
|
||||||
let request = (pinned ? Status.unpin : Status.pin)(status.id)
|
let request = (pinned ? Status.unpin : Status.pin)(status.id)
|
||||||
self.mastodonController?.run(request, completion: { [weak self] (response) in
|
self.mastodonController?.run(request, completion: { [weak self] (response) in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
switch response {
|
if case let .success(status, _) = response {
|
||||||
case .success(let status, _):
|
self.mastodonController?.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
self.mastodonController?.persistentContainer.addOrUpdate(status: status)
|
|
||||||
case .failure(let error):
|
|
||||||
if let toastable = self.toastableViewController {
|
|
||||||
let config = ToastConfiguration(from: error, with: "Error \(pinned ? "Unp" :"P")inning", in: toastable, retryAction: nil)
|
|
||||||
toastable.showToast(configuration: config, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
@ -209,16 +186,10 @@ extension MenuActionProvider {
|
||||||
guard let mastodonController = self?.mastodonController else { return }
|
guard let mastodonController = self?.mastodonController else { return }
|
||||||
let request = Client.getStatus(id: status.id)
|
let request = Client.getStatus(id: status.id)
|
||||||
mastodonController.run(request, completion: { (response) in
|
mastodonController.run(request, completion: { (response) in
|
||||||
switch response {
|
if case let .success(status, _) = response {
|
||||||
case .success(let status, _):
|
|
||||||
// todo: this shouldn't really use the viewContext, but for some reason saving the
|
// todo: this shouldn't really use the viewContext, but for some reason saving the
|
||||||
// backgroundContext with the new version of the status isn't updating the viewContext
|
// backgroundContext with the new version of the status isn't updating the viewContext
|
||||||
mastodonController.persistentContainer.addOrUpdate(status: status, context: mastodonController.persistentContainer.viewContext)
|
mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false, context: mastodonController.persistentContainer.viewContext)
|
||||||
case .failure(let error):
|
|
||||||
if let toastable = self?.toastableViewController {
|
|
||||||
let config = ToastConfiguration(from: error, with: "Error Refreshing Poll", in: toastable, retryAction: nil)
|
|
||||||
toastable.showToast(configuration: config, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}), at: 0)
|
}), at: 0)
|
||||||
|
@ -286,7 +257,7 @@ extension MenuActionProvider {
|
||||||
actions.append(UIWindowScene.ActivationAction { (_) in
|
actions.append(UIWindowScene.ActivationAction { (_) in
|
||||||
return .init(userActivity: activity(), options: options, preview: nil)
|
return .init(userActivity: activity(), options: options, preview: nil)
|
||||||
})
|
})
|
||||||
} else if UIApplication.shared.supportsMultipleScenes {
|
} else if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
actions.append(createAction(identifier: "new_window", title: "Open in New Window", systemImageName: "rectangle.badge.plus", handler: { (_) in
|
actions.append(createAction(identifier: "new_window", title: "Open in New Window", systemImageName: "rectangle.badge.plus", handler: { (_) in
|
||||||
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity(), options: nil, errorHandler: nil)
|
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity(), options: nil, errorHandler: nil)
|
||||||
}))
|
}))
|
||||||
|
@ -308,11 +279,8 @@ extension MenuActionProvider {
|
||||||
let request = (following ? Account.unfollow : Account.follow)(accountID)
|
let request = (following ? Account.unfollow : Account.follow)(accountID)
|
||||||
mastodonController.run(request) { response in
|
mastodonController.run(request) { response in
|
||||||
switch response {
|
switch response {
|
||||||
case .failure(let error):
|
case .failure(_):
|
||||||
if let toastable = self.toastableViewController {
|
fatalError()
|
||||||
let config = ToastConfiguration(from: error, with: "Error \(following ? "Unf" : "F")ollowing", in: toastable, retryAction: nil)
|
|
||||||
toastable.showToast(configuration: config, animated: true)
|
|
||||||
}
|
|
||||||
case .success(let relationship, _):
|
case .success(let relationship, _):
|
||||||
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
|
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import SwiftSoup
|
||||||
|
|
||||||
class AccountTableViewCell: UITableViewCell {
|
class AccountTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
weak var delegate: (TuskerNavigationDelegate & MenuActionProvider)?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var avatarImageView: UIImageView!
|
@IBOutlet weak var avatarImageView: UIImageView!
|
||||||
|
@ -98,11 +98,13 @@ extension AccountTableViewCell: SelectableTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AccountTableViewCell: MenuPreviewProvider {
|
extension AccountTableViewCell: MenuPreviewProvider {
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||||
|
|
||||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||||
guard let mastodonController = mastodonController else { return nil }
|
guard let mastodonController = mastodonController else { return nil }
|
||||||
return (
|
return (
|
||||||
content: { ProfileViewController(accountID: self.accountID, mastodonController: mastodonController) },
|
content: { ProfileViewController(accountID: self.accountID, mastodonController: mastodonController) },
|
||||||
actions: { self.delegate?.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView) ?? [] }
|
actions: { self.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,10 +243,9 @@ extension ContentTextView: UITextViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ContentTextView: MenuActionProvider {
|
extension ContentTextView: MenuPreviewProvider {
|
||||||
var toastableViewController: ToastableViewController? {
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||||
// todo: pass this down through the text view
|
fatalError("unimplemented")
|
||||||
nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import SwiftSoup
|
||||||
|
|
||||||
class ActionNotificationGroupTableViewCell: UITableViewCell {
|
class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
weak var delegate: (TuskerNavigationDelegate & MenuActionProvider)?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var actionImageView: UIImageView!
|
@IBOutlet weak var actionImageView: UIImageView!
|
||||||
|
@ -244,6 +244,8 @@ extension ActionNotificationGroupTableViewCell: SelectableTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
|
extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||||
|
|
||||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||||
return (content: {
|
return (content: {
|
||||||
let notifications = self.group.notifications
|
let notifications = self.group.notifications
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
||||||
|
|
||||||
class FollowNotificationGroupTableViewCell: UITableViewCell {
|
class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
weak var delegate: (TuskerNavigationDelegate & MenuActionProvider)?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var avatarStackView: UIStackView!
|
@IBOutlet weak var avatarStackView: UIStackView!
|
||||||
|
@ -196,6 +196,8 @@ extension FollowNotificationGroupTableViewCell: SelectableTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
|
extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||||
|
|
||||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||||
guard let mastodonController = mastodonController else { return nil }
|
guard let mastodonController = mastodonController else { return nil }
|
||||||
let accountIDs = self.group.notifications.map { $0.account.id }
|
let accountIDs = self.group.notifications.map { $0.account.id }
|
||||||
|
@ -207,7 +209,7 @@ extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
|
||||||
}
|
}
|
||||||
}, actions: {
|
}, actions: {
|
||||||
if accountIDs.count == 1 {
|
if accountIDs.count == 1 {
|
||||||
return self.delegate?.actionsForProfile(accountID: accountIDs.first!, sourceView: self) ?? []
|
return self.actionsForProfile(accountID: accountIDs.first!, sourceView: self)
|
||||||
} else {
|
} else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Pachyderm
|
||||||
|
|
||||||
class FollowRequestNotificationTableViewCell: UITableViewCell {
|
class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
weak var delegate: (TuskerNavigationDelegate & MenuActionProvider)?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var stackView: UIStackView!
|
@IBOutlet weak var stackView: UIStackView!
|
||||||
|
@ -169,6 +169,8 @@ extension FollowRequestNotificationTableViewCell: SelectableTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FollowRequestNotificationTableViewCell: MenuPreviewProvider {
|
extension FollowRequestNotificationTableViewCell: MenuPreviewProvider {
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||||
|
|
||||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||||
guard let mastodonController = mastodonController else { return nil }
|
guard let mastodonController = mastodonController else { return nil }
|
||||||
return (content: {
|
return (content: {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import SwiftSoup
|
||||||
|
|
||||||
class PollFinishedTableViewCell: UITableViewCell {
|
class PollFinishedTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
weak var delegate: (TuskerNavigationDelegate & MenuActionProvider)?
|
weak var delegate: TuskerNavigationDelegate?
|
||||||
var mastodonController: MastodonController? { delegate?.apiController }
|
var mastodonController: MastodonController? { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
@IBOutlet weak var displayNameLabel: EmojiLabel!
|
||||||
|
@ -91,6 +91,8 @@ extension PollFinishedTableViewCell: SelectableTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PollFinishedTableViewCell: MenuPreviewProvider {
|
extension PollFinishedTableViewCell: MenuPreviewProvider {
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { delegate }
|
||||||
|
|
||||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||||
guard let delegate = delegate,
|
guard let delegate = delegate,
|
||||||
let statusID = notification?.status?.id,
|
let statusID = notification?.status?.id,
|
||||||
|
@ -100,7 +102,7 @@ extension PollFinishedTableViewCell: MenuPreviewProvider {
|
||||||
return (content: {
|
return (content: {
|
||||||
delegate.conversation(mainStatusID: statusID, state: .unknown)
|
delegate.conversation(mainStatusID: statusID, state: .unknown)
|
||||||
}, actions: {
|
}, actions: {
|
||||||
delegate.actionsForStatus(status, sourceView: self)
|
self.actionsForStatus(status, sourceView: self)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
//
|
|
||||||
// PollVoteButton.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 5/1/22.
|
|
||||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
/// Wraps a UILabel and UIButton to allow setting disabled titles on Catalyst, where `setTitle(_:for:)` only works for the normal state.
|
|
||||||
class PollVoteButton: UIView {
|
|
||||||
|
|
||||||
var disabledTitle: String = "" {
|
|
||||||
didSet {
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var isEnabled = true {
|
|
||||||
didSet {
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var button = UIButton(type: .system)
|
|
||||||
#if targetEnvironment(macCatalyst)
|
|
||||||
private var label = UILabel()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
|
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
button.setTitleColor(.secondaryLabel, for: .disabled)
|
|
||||||
embedSubview(button)
|
|
||||||
#if targetEnvironment(macCatalyst)
|
|
||||||
label.textColor = .secondaryLabel
|
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
embedSubview(label)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func addTarget(_ target: Any, action: Selector) {
|
|
||||||
button.addTarget(target, action: action, for: .touchUpInside)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setFont(_ font: UIFont) {
|
|
||||||
button.titleLabel!.font = font
|
|
||||||
#if targetEnvironment(macCatalyst)
|
|
||||||
label.font = font
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private func update() {
|
|
||||||
button.isEnabled = isEnabled
|
|
||||||
if isEnabled {
|
|
||||||
#if targetEnvironment(macCatalyst)
|
|
||||||
label.isHidden = true
|
|
||||||
button.isHidden = false
|
|
||||||
#endif
|
|
||||||
button.setTitle("Vote", for: .normal)
|
|
||||||
} else {
|
|
||||||
#if targetEnvironment(macCatalyst)
|
|
||||||
label.text = disabledTitle
|
|
||||||
label.isHidden = false
|
|
||||||
button.isHidden = true
|
|
||||||
#else
|
|
||||||
button.setTitle(disabledTitle, for: .disabled)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -26,7 +26,7 @@ class StatusPollView: UIView {
|
||||||
private(set) var poll: Poll?
|
private(set) var poll: Poll?
|
||||||
|
|
||||||
private var optionsView: PollOptionsView!
|
private var optionsView: PollOptionsView!
|
||||||
private var voteButton: PollVoteButton!
|
private var voteButton: UIButton!
|
||||||
private var infoLabel: UILabel!
|
private var infoLabel: UILabel!
|
||||||
|
|
||||||
private var canVote = true
|
private var canVote = true
|
||||||
|
@ -54,16 +54,12 @@ class StatusPollView: UIView {
|
||||||
infoLabel.adjustsFontSizeToFitWidth = true
|
infoLabel.adjustsFontSizeToFitWidth = true
|
||||||
addSubview(infoLabel)
|
addSubview(infoLabel)
|
||||||
|
|
||||||
// voteButton = UIButton(type: .system)
|
voteButton = UIButton(type: .system)
|
||||||
// voteButton.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
// voteButton.addTarget(self, action: #selector(votePressed), for: .touchUpInside)
|
|
||||||
// voteButton.setTitle("Vote", for: .normal)
|
|
||||||
// voteButton.setTitleColor(.secondaryLabel, for: .disabled)
|
|
||||||
// voteButton.titleLabel!.font = infoLabel.font
|
|
||||||
voteButton = PollVoteButton()
|
|
||||||
voteButton.translatesAutoresizingMaskIntoConstraints = false
|
voteButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
voteButton.addTarget(self, action: #selector(votePressed))
|
voteButton.addTarget(self, action: #selector(votePressed), for: .touchUpInside)
|
||||||
voteButton.setFont(infoLabel.font)
|
voteButton.setTitle("Vote", for: .normal)
|
||||||
|
voteButton.setTitleColor(.secondaryLabel, for: .disabled)
|
||||||
|
voteButton.titleLabel!.font = infoLabel.font
|
||||||
addSubview(voteButton)
|
addSubview(voteButton)
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
@ -121,17 +117,17 @@ class StatusPollView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
if expired {
|
if expired {
|
||||||
voteButton.disabledTitle = "Expired"
|
voteButton.setTitle("Expired", for: .disabled)
|
||||||
} else if poll.voted ?? false {
|
} else if poll.voted ?? false {
|
||||||
if status.account.id == mastodonController.account.id {
|
if status.account.id == mastodonController.account.id {
|
||||||
voteButton.isHidden = true
|
voteButton.setTitle("", for: .disabled)
|
||||||
} else {
|
} else {
|
||||||
voteButton.disabledTitle = "Voted"
|
voteButton.setTitle("Voted", for: .disabled)
|
||||||
}
|
}
|
||||||
} else if poll.multiple {
|
} else if poll.multiple {
|
||||||
voteButton.disabledTitle = "Select multiple"
|
voteButton.setTitle("Select multiple", for: .disabled)
|
||||||
} else {
|
} else {
|
||||||
voteButton.disabledTitle = "Select one"
|
voteButton.setTitle("Select one", for: .disabled)
|
||||||
}
|
}
|
||||||
voteButton.isEnabled = false
|
voteButton.isEnabled = false
|
||||||
}
|
}
|
||||||
|
@ -143,7 +139,7 @@ class StatusPollView: UIView {
|
||||||
@objc private func votePressed() {
|
@objc private func votePressed() {
|
||||||
optionsView.isEnabled = false
|
optionsView.isEnabled = false
|
||||||
voteButton.isEnabled = false
|
voteButton.isEnabled = false
|
||||||
voteButton.disabledTitle = "Voted"
|
voteButton.setTitle("Voted", for: .disabled)
|
||||||
|
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
protocol ProfileHeaderViewDelegate: TuskerNavigationDelegate, MenuActionProvider {
|
protocol ProfileHeaderViewDelegate: TuskerNavigationDelegate {
|
||||||
func profileHeader(_ headerView: ProfileHeaderView, selectedPostsIndexChangedTo newIndex: Int)
|
func profileHeader(_ headerView: ProfileHeaderView, selectedPostsIndexChangedTo newIndex: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ class ProfileHeaderView: UIView {
|
||||||
|
|
||||||
updateImages(account: account)
|
updateImages(account: account)
|
||||||
|
|
||||||
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForProfile(accountID: accountID, sourceView: moreButton) ?? [])
|
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForProfile(accountID: accountID, sourceView: moreButton))
|
||||||
|
|
||||||
noteTextView.navigationDelegate = delegate
|
noteTextView.navigationDelegate = delegate
|
||||||
noteTextView.setTextFromHtml(account.note)
|
noteTextView.setTextFromHtml(account.note)
|
||||||
|
@ -272,3 +272,7 @@ extension ProfileHeaderView: UIPointerInteractionDelegate {
|
||||||
return UIPointerStyle(effect: .lift(preview), shape: .none)
|
return UIPointerStyle(effect: .lift(preview), shape: .none)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ProfileHeaderView: MenuPreviewProvider {
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { delegate }
|
||||||
|
}
|
||||||
|
|
|
@ -11,11 +11,11 @@ import Pachyderm
|
||||||
import Combine
|
import Combine
|
||||||
import AVKit
|
import AVKit
|
||||||
|
|
||||||
protocol StatusTableViewCellDelegate: TuskerNavigationDelegate, MenuActionProvider {
|
protocol StatusTableViewCellDelegate: TuskerNavigationDelegate {
|
||||||
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell)
|
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell)
|
||||||
}
|
}
|
||||||
|
|
||||||
class BaseStatusTableViewCell: UITableViewCell {
|
class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
|
||||||
|
|
||||||
weak var delegate: StatusTableViewCellDelegate? {
|
weak var delegate: StatusTableViewCellDelegate? {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -153,8 +153,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
cardView.card = status.card
|
cardView.card = status.card
|
||||||
cardView.isHidden = status.card == nil
|
cardView.isHidden = status.card == nil
|
||||||
cardView.navigationDelegate = delegate
|
cardView.navigationDelegate = navigationDelegate
|
||||||
cardView.actionProvider = delegate
|
|
||||||
|
|
||||||
attachmentsView.updateUI(status: status)
|
attachmentsView.updateUI(status: status)
|
||||||
|
|
||||||
|
@ -207,7 +206,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
// keep menu in sync with changed states e.g. bookmarked, muted
|
// keep menu in sync with changed states e.g. bookmarked, muted
|
||||||
// do not include reply action here, because the cell already contains a button for it
|
// do not include reply action here, because the cell already contains a button for it
|
||||||
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForStatus(status, sourceView: moreButton, includeReply: false) ?? [])
|
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForStatus(status, sourceView: moreButton, includeReply: false))
|
||||||
|
|
||||||
pollView.isHidden = status.poll == nil
|
pollView.isHidden = status.poll == nil
|
||||||
pollView.mastodonController = mastodonController
|
pollView.mastodonController = mastodonController
|
||||||
|
@ -327,6 +326,13 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
showStatusAutomatically = false
|
showStatusAutomatically = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - MenuPreviewProvider
|
||||||
|
var navigationDelegate: TuskerNavigationDelegate? { return delegate }
|
||||||
|
|
||||||
|
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Interaction
|
// MARK: - Interaction
|
||||||
|
|
||||||
@IBAction func collapseButtonPressed() {
|
@IBAction func collapseButtonPressed() {
|
||||||
|
@ -387,7 +393,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
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 ?? false
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
} else {
|
} else {
|
||||||
self.favorited = oldValue
|
self.favorited = oldValue
|
||||||
|
@ -429,7 +435,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
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 ?? false
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus)
|
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
} else {
|
} else {
|
||||||
self.reblogged = oldValue
|
self.reblogged = oldValue
|
||||||
|
|
|
@ -112,7 +112,7 @@ extension ConversationMainStatusTableViewCell: UIContextMenuInteractionDelegate
|
||||||
return UIContextMenuConfiguration(identifier: nil) {
|
return UIContextMenuConfiguration(identifier: nil) {
|
||||||
ProfileViewController(accountID: self.accountID, mastodonController: self.mastodonController)
|
ProfileViewController(accountID: self.accountID, mastodonController: self.mastodonController)
|
||||||
} actionProvider: { (_) in
|
} actionProvider: { (_) in
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: self.delegate?.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView) ?? [])
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: self.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import WebURLFoundationExtras
|
||||||
class StatusCardView: UIView {
|
class StatusCardView: UIView {
|
||||||
|
|
||||||
weak var navigationDelegate: TuskerNavigationDelegate?
|
weak var navigationDelegate: TuskerNavigationDelegate?
|
||||||
weak var actionProvider: MenuActionProvider?
|
|
||||||
|
|
||||||
var card: Card? {
|
var card: Card? {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -213,6 +212,9 @@ class StatusCardView: UIView {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension StatusCardView: MenuPreviewProvider {
|
||||||
|
}
|
||||||
|
|
||||||
extension StatusCardView: UIContextMenuInteractionDelegate {
|
extension StatusCardView: UIContextMenuInteractionDelegate {
|
||||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
guard let card = card else { return nil }
|
guard let card = card else { return nil }
|
||||||
|
@ -220,7 +222,7 @@ extension StatusCardView: UIContextMenuInteractionDelegate {
|
||||||
return UIContextMenuConfiguration(identifier: nil) {
|
return UIContextMenuConfiguration(identifier: nil) {
|
||||||
return SFSafariViewController(url: URL(card.url)!)
|
return SFSafariViewController(url: URL(card.url)!)
|
||||||
} actionProvider: { (_) in
|
} actionProvider: { (_) in
|
||||||
let actions = self.actionProvider?.actionsForURL(URL(card.url)!, sourceView: self) ?? []
|
let actions = self.actionsForURL(URL(card.url)!, sourceView: self)
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,6 +214,17 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
reply()
|
reply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> BaseStatusTableViewCell.PreviewProviders? {
|
||||||
|
guard let mastodonController = mastodonController,
|
||||||
|
let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
content: { ConversationTableViewController(for: self.statusID, state: self.statusState.copy(), mastodonController: mastodonController) },
|
||||||
|
actions: { self.actionsForStatus(status, sourceView: self) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Accessibility
|
// MARK: - Accessibility
|
||||||
|
|
||||||
override var accessibilityLabel: String? {
|
override var accessibilityLabel: String? {
|
||||||
|
@ -282,7 +293,7 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
completion(true)
|
completion(true)
|
||||||
mastodonController.persistentContainer.addOrUpdate(status: status)
|
mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -309,7 +320,7 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
completion(true)
|
completion(true)
|
||||||
mastodonController.persistentContainer.addOrUpdate(status: status)
|
mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -366,13 +377,13 @@ extension TimelineStatusTableViewCell: UIContextMenuInteractionDelegate {
|
||||||
return UIContextMenuConfiguration(identifier: nil) {
|
return UIContextMenuConfiguration(identifier: nil) {
|
||||||
ProfileViewController(accountID: self.accountID, mastodonController: self.mastodonController)
|
ProfileViewController(accountID: self.accountID, mastodonController: self.mastodonController)
|
||||||
} actionProvider: { (_) in
|
} actionProvider: { (_) in
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: self.delegate?.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView) ?? [])
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: self.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||||
if let viewController = animator.previewViewController,
|
if let viewController = animator.previewViewController,
|
||||||
let delegate = delegate {
|
let delegate = navigationDelegate {
|
||||||
animator.preferredCommitStyle = .pop
|
animator.preferredCommitStyle = .pop
|
||||||
animator.addCompletion {
|
animator.addCompletion {
|
||||||
if let customPresenting = viewController as? CustomPreviewPresenting {
|
if let customPresenting = viewController as? CustomPreviewPresenting {
|
||||||
|
@ -385,16 +396,3 @@ extension TimelineStatusTableViewCell: UIContextMenuInteractionDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineStatusTableViewCell: MenuPreviewProvider {
|
|
||||||
func getPreviewProviders(for location: CGPoint, sourceViewController: UIViewController) -> PreviewProviders? {
|
|
||||||
guard let mastodonController = mastodonController,
|
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
content: { ConversationTableViewController(for: self.statusID, state: self.statusState.copy(), mastodonController: mastodonController) },
|
|
||||||
actions: { self.delegate?.actionsForStatus(status, sourceView: self) ?? [] }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,14 +35,12 @@ struct ToastConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ToastConfiguration {
|
extension ToastConfiguration {
|
||||||
init(from error: Client.Error, with title: String, in viewController: UIViewController, retryAction: ((ToastView) -> Void)?) {
|
init(from error: Client.Error, with title: String, in viewController: UIViewController, retryAction: @escaping (ToastView) -> Void) {
|
||||||
self.init(title: title)
|
self.init(title: title)
|
||||||
self.subtitle = error.localizedDescription
|
self.subtitle = error.localizedDescription
|
||||||
self.systemImageName = error.systemImageName
|
self.systemImageName = error.systemImageName
|
||||||
if let retryAction = retryAction {
|
self.actionTitle = "Retry"
|
||||||
self.actionTitle = "Retry"
|
self.action = retryAction
|
||||||
self.action = retryAction
|
|
||||||
}
|
|
||||||
self.longPressAction = { [unowned viewController] toast in
|
self.longPressAction = { [unowned viewController] toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
let text = """
|
let text = """
|
||||||
|
|
Loading…
Reference in New Issue