Compare commits
No commits in common. "02088b1f55a263d13b66e039059c309458679489" and "fa1daa682fc94630392e5c4ae48b5d10de9d21e5" have entirely different histories.
02088b1f55
...
fa1daa682f
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public final class Account: AccountProtocol, Decodable {
|
public class Account: Decodable {
|
||||||
public let id: String
|
public let id: String
|
||||||
public let username: String
|
public let username: String
|
||||||
public let acct: String
|
public let acct: String
|
||||||
|
@ -27,7 +27,7 @@ public final class Account: AccountProtocol, Decodable {
|
||||||
public private(set) var emojis: [Emoji]
|
public private(set) var emojis: [Emoji]
|
||||||
public let moved: Bool?
|
public let moved: Bool?
|
||||||
public let movedTo: Account?
|
public let movedTo: Account?
|
||||||
public let fields: [Field]
|
public let fields: [Field]?
|
||||||
public let bot: Bool?
|
public let bot: Bool?
|
||||||
|
|
||||||
public required init(from decoder: Decoder) throws {
|
public required init(from decoder: Decoder) throws {
|
||||||
|
@ -49,7 +49,7 @@ public final class Account: AccountProtocol, Decodable {
|
||||||
self.header = try container.decode(URL.self, forKey: .header)
|
self.header = try container.decode(URL.self, forKey: .header)
|
||||||
self.headerStatic = try container.decode(URL.self, forKey: .url)
|
self.headerStatic = try container.decode(URL.self, forKey: .url)
|
||||||
self.emojis = try container.decode([Emoji].self, forKey: .emojis)
|
self.emojis = try container.decode([Emoji].self, forKey: .emojis)
|
||||||
self.fields = (try? container.decode([Field].self, forKey: .fields)) ?? []
|
self.fields = try? container.decode([Field].self, forKey: .fields)
|
||||||
self.bot = try? container.decode(Bool.self, forKey: .bot)
|
self.bot = try? container.decode(Bool.self, forKey: .bot)
|
||||||
|
|
||||||
if let moved = try? container.decode(Bool.self, forKey: .moved) {
|
if let moved = try? container.decode(Bool.self, forKey: .moved) {
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// AccountProtocol.swift
|
|
||||||
// Pachyderm
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 4/11/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public protocol AccountProtocol {
|
|
||||||
associatedtype Account: AccountProtocol
|
|
||||||
|
|
||||||
var id: String { get }
|
|
||||||
var username: String { get }
|
|
||||||
var acct: String { get }
|
|
||||||
var displayName: String { get }
|
|
||||||
var locked: Bool { get }
|
|
||||||
var createdAt: Date { get }
|
|
||||||
var followersCount: Int { get }
|
|
||||||
var followingCount: Int { get }
|
|
||||||
var statusesCount: Int { get }
|
|
||||||
var note: String { get }
|
|
||||||
var url: URL { get }
|
|
||||||
var avatar: URL { get }
|
|
||||||
var header: URL { get }
|
|
||||||
var moved: Bool? { get }
|
|
||||||
var bot: Bool? { get }
|
|
||||||
|
|
||||||
var movedTo: Account? { get }
|
|
||||||
var emojis: [Emoji] { get }
|
|
||||||
var fields: [Pachyderm.Account.Field] { get }
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
//
|
|
||||||
// StatusProtocol.swift
|
|
||||||
// Pachyderm
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 4/11/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public protocol StatusProtocol {
|
|
||||||
associatedtype Status: StatusProtocol
|
|
||||||
associatedtype Account: AccountProtocol
|
|
||||||
|
|
||||||
var id: String { get }
|
|
||||||
var uri: String { get }
|
|
||||||
var inReplyToID: String? { get }
|
|
||||||
var inReplyToAccountID: String? { get }
|
|
||||||
var content: String { get }
|
|
||||||
var createdAt: Date { get }
|
|
||||||
var reblogsCount: Int { get }
|
|
||||||
var favouritesCount: Int { get }
|
|
||||||
var reblogged: Bool { get }
|
|
||||||
var favourited: Bool { get }
|
|
||||||
var sensitive: Bool { get }
|
|
||||||
var spoilerText: String { get }
|
|
||||||
var visibility: Pachyderm.Status.Visibility { get }
|
|
||||||
var applicationName: String? { get }
|
|
||||||
var pinned: Bool? { get }
|
|
||||||
var bookmarked: Bool? { get }
|
|
||||||
|
|
||||||
var account: Account { get }
|
|
||||||
var reblog: Status? { get }
|
|
||||||
var attachments: [Attachment] { get }
|
|
||||||
var emojis: [Emoji] { get }
|
|
||||||
var hashtags: [Hashtag] { get }
|
|
||||||
var mentions: [Mention] { get }
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public final class Status: StatusProtocol, Decodable {
|
public class Status: Decodable {
|
||||||
public let id: String
|
public let id: String
|
||||||
public let uri: String
|
public let uri: String
|
||||||
public let url: URL?
|
public let url: URL?
|
||||||
|
@ -23,8 +23,8 @@ public final class Status: StatusProtocol, Decodable {
|
||||||
// public let repliesCount: Int
|
// public let repliesCount: Int
|
||||||
public let reblogsCount: Int
|
public let reblogsCount: Int
|
||||||
public let favouritesCount: Int
|
public let favouritesCount: Int
|
||||||
public let reblogged: Bool
|
public let reblogged: Bool?
|
||||||
public let favourited: Bool
|
public let favourited: Bool?
|
||||||
public let muted: Bool?
|
public let muted: Bool?
|
||||||
public let sensitive: Bool
|
public let sensitive: Bool
|
||||||
public let spoilerText: String
|
public let spoilerText: String
|
||||||
|
@ -38,8 +38,6 @@ public final class Status: StatusProtocol, Decodable {
|
||||||
public let bookmarked: Bool?
|
public let bookmarked: Bool?
|
||||||
public let card: Card?
|
public let card: Card?
|
||||||
|
|
||||||
public var applicationName: String? { application?.name }
|
|
||||||
|
|
||||||
public static func getContext(_ statusID: String) -> Request<ConversationContext> {
|
public static func getContext(_ statusID: String) -> Request<ConversationContext> {
|
||||||
return Request<ConversationContext>(method: .get, path: "/api/v1/statuses/\(statusID)/context")
|
return Request<ConversationContext>(method: .get, path: "/api/v1/statuses/\(statusID)/context")
|
||||||
}
|
}
|
||||||
|
@ -48,14 +46,14 @@ public final class Status: StatusProtocol, Decodable {
|
||||||
return Request<Card>(method: .get, path: "/api/v1/statuses/\(status.id)/card")
|
return Request<Card>(method: .get, path: "/api/v1/statuses/\(status.id)/card")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getFavourites(_ statusID: String, range: RequestRange = .default) -> Request<[Account]> {
|
public static func getFavourites(_ status: Status, range: RequestRange = .default) -> Request<[Account]> {
|
||||||
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(statusID)/favourited_by")
|
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(status.id)/favourited_by")
|
||||||
request.range = range
|
request.range = range
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getReblogs(_ statusID: String, range: RequestRange = .default) -> Request<[Account]> {
|
public static func getReblogs(_ status: Status, range: RequestRange = .default) -> Request<[Account]> {
|
||||||
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(statusID)/reblogged_by")
|
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(status.id)/reblogged_by")
|
||||||
request.range = range
|
request.range = range
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
@ -64,20 +62,20 @@ public final class Status: StatusProtocol, Decodable {
|
||||||
return Request<Empty>(method: .delete, path: "/api/v1/statuses/\(status.id)")
|
return Request<Empty>(method: .delete, path: "/api/v1/statuses/\(status.id)")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func reblog(_ statusID: String) -> Request<Status> {
|
public static func reblog(_ status: Status) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/reblog")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/reblog")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func unreblog(_ statusID: String) -> Request<Status> {
|
public static func unreblog(_ status: Status) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unreblog")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/unreblog")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func favourite(_ statusID: String) -> Request<Status> {
|
public static func favourite(_ status: Status) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/favourite")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/favourite")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func unfavourite(_ statusID: String) -> Request<Status> {
|
public static func unfavourite(_ status: Status) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unfavourite")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/unfavourite")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func pin(_ status: Status) -> Request<Status> {
|
public static func pin(_ status: Status) -> Request<Status> {
|
||||||
|
@ -92,8 +90,8 @@ public final class Status: StatusProtocol, Decodable {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/bookmark")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/bookmark")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func unbookmark(_ statusID: String) -> Request<Status> {
|
public static func unbookmark(_ status: Status) -> Request<Status> {
|
||||||
return Request<Status>(method: .post, path: "/api/v1/statuses/\(statusID)/unbookmark")
|
return Request<Status>(method: .post, path: "/api/v1/statuses/\(status.id)/unbookmark")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func muteConversation(_ status: Status) -> Request<Status> {
|
public static func muteConversation(_ status: Status) -> Request<Status> {
|
||||||
|
|
|
@ -9,14 +9,14 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class NotificationGroup {
|
public class NotificationGroup {
|
||||||
public let notifications: [Notification]
|
public let notificationIDs: [String]
|
||||||
public let id: String
|
public let id: String
|
||||||
public let kind: Notification.Kind
|
public let kind: Notification.Kind
|
||||||
public let statusState: StatusState?
|
public let statusState: StatusState?
|
||||||
|
|
||||||
init?(notifications: [Notification]) {
|
init?(notifications: [Notification]) {
|
||||||
guard !notifications.isEmpty else { return nil }
|
guard !notifications.isEmpty else { return nil }
|
||||||
self.notifications = notifications
|
self.notificationIDs = notifications.map { $0.id }
|
||||||
self.id = notifications.first!.id
|
self.id = notifications.first!.id
|
||||||
self.kind = notifications.first!.kind
|
self.kind = notifications.first!.kind
|
||||||
if kind == .mention {
|
if kind == .mention {
|
||||||
|
|
|
@ -20,14 +20,13 @@
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
||||||
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
|
||||||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
|
||||||
|
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; };
|
||||||
D60309B52419D4F100A465FF /* ComposeAttachmentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */; };
|
D60309B52419D4F100A465FF /* ComposeAttachmentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */; };
|
||||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
||||||
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; };
|
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; };
|
||||||
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F252442372B005F8713 /* AccountMO.swift */; };
|
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F252442372B005F8713 /* AccountMO.swift */; };
|
||||||
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */; };
|
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */; };
|
||||||
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */; };
|
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */; };
|
||||||
D60E2F3124424F1A005F8713 /* StatusProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F3024424F1A005F8713 /* StatusProtocol.swift */; };
|
|
||||||
D60E2F3324425374005F8713 /* AccountProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F3224425374005F8713 /* AccountProtocol.swift */; };
|
|
||||||
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
||||||
D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.swift */; };
|
D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.swift */; };
|
||||||
D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */ = {isa = PBXBuildFile; fileRef = D61099AD2144B0CC00432DC2 /* Pachyderm.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */ = {isa = PBXBuildFile; fileRef = D61099AD2144B0CC00432DC2 /* Pachyderm.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
@ -132,7 +131,6 @@
|
||||||
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC19123C271D9000D0238 /* MastodonActivity.swift */; };
|
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC19123C271D9000D0238 /* MastodonActivity.swift */; };
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||||
D64D8CA92463B494006B0BAA /* CachedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* CachedDictionary.swift */; };
|
|
||||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
|
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
|
||||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; };
|
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; };
|
||||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
|
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
|
||||||
|
@ -310,6 +308,7 @@
|
||||||
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
||||||
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
|
||||||
|
D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
|
||||||
D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentsViewController.swift; sourceTree = "<group>"; };
|
D60309B42419D4F100A465FF /* ComposeAttachmentsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAttachmentsViewController.swift; sourceTree = "<group>"; };
|
||||||
D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
|
D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
|
||||||
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
|
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
@ -317,8 +316,6 @@
|
||||||
D60E2F252442372B005F8713 /* AccountMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMO.swift; sourceTree = "<group>"; };
|
D60E2F252442372B005F8713 /* AccountMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMO.swift; sourceTree = "<group>"; };
|
||||||
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazilyDecoding.swift; sourceTree = "<group>"; };
|
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazilyDecoding.swift; sourceTree = "<group>"; };
|
||||||
D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCachePersistentStore.swift; sourceTree = "<group>"; };
|
D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCachePersistentStore.swift; sourceTree = "<group>"; };
|
||||||
D60E2F3024424F1A005F8713 /* StatusProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusProtocol.swift; sourceTree = "<group>"; };
|
|
||||||
D60E2F3224425374005F8713 /* AccountProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountProtocol.swift; sourceTree = "<group>"; };
|
|
||||||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D61099AD2144B0CC00432DC2 /* Pachyderm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pachyderm.h; sourceTree = "<group>"; };
|
D61099AD2144B0CC00432DC2 /* Pachyderm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pachyderm.h; sourceTree = "<group>"; };
|
||||||
D61099AE2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D61099AE2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
@ -423,7 +420,6 @@
|
||||||
D64BC19123C271D9000D0238 /* MastodonActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonActivity.swift; sourceTree = "<group>"; };
|
D64BC19123C271D9000D0238 /* MastodonActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonActivity.swift; sourceTree = "<group>"; };
|
||||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
||||||
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
||||||
D64D8CA82463B494006B0BAA /* CachedDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedDictionary.swift; sourceTree = "<group>"; };
|
|
||||||
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
|
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
|
||||||
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSwipeActionProvider.swift; sourceTree = "<group>"; };
|
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSwipeActionProvider.swift; sourceTree = "<group>"; };
|
||||||
D65F612D23AE990C00F3CFD3 /* Embassy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Embassy.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D65F612D23AE990C00F3CFD3 /* Embassy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Embassy.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -595,15 +591,6 @@
|
||||||
path = "Attachment Gallery";
|
path = "Attachment Gallery";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D60E2F2F24424F0D005F8713 /* Protocols */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D60E2F3024424F1A005F8713 /* StatusProtocol.swift */,
|
|
||||||
D60E2F3224425374005F8713 /* AccountProtocol.swift */,
|
|
||||||
);
|
|
||||||
path = Protocols;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D61099AC2144B0CC00432DC2 /* Pachyderm */ = {
|
D61099AC2144B0CC00432DC2 /* Pachyderm */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -663,7 +650,6 @@
|
||||||
D61099DD2144C10C00432DC2 /* Model */ = {
|
D61099DD2144C10C00432DC2 /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D60E2F2F24424F0D005F8713 /* Protocols */,
|
|
||||||
D61099DE2144C11400432DC2 /* MastodonError.swift */,
|
D61099DE2144C11400432DC2 /* MastodonError.swift */,
|
||||||
D6109A04214572BF00432DC2 /* Scope.swift */,
|
D6109A04214572BF00432DC2 /* Scope.swift */,
|
||||||
D61099E02144C1DC00432DC2 /* Account.swift */,
|
D61099E02144C1DC00432DC2 /* Account.swift */,
|
||||||
|
@ -1248,9 +1234,9 @@
|
||||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||||
D627FF75217E923E00CC0648 /* DraftsManager.swift */,
|
D627FF75217E923E00CC0648 /* DraftsManager.swift */,
|
||||||
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */,
|
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */,
|
||||||
|
D6028B9A2150811100F223B9 /* MastodonCache.swift */,
|
||||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
||||||
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
||||||
D64D8CA82463B494006B0BAA /* CachedDictionary.swift */,
|
|
||||||
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
||||||
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
D6F1F84E2193B9BE00F5FE67 /* Caching */,
|
||||||
D6757A7A2157E00100721E32 /* XCallbackURL */,
|
D6757A7A2157E00100721E32 /* XCallbackURL */,
|
||||||
|
@ -1595,9 +1581,7 @@
|
||||||
D61099CB2144B20500432DC2 /* Request.swift in Sources */,
|
D61099CB2144B20500432DC2 /* Request.swift in Sources */,
|
||||||
D6109A05214572BF00432DC2 /* Scope.swift in Sources */,
|
D6109A05214572BF00432DC2 /* Scope.swift in Sources */,
|
||||||
D6109A11214607D500432DC2 /* Timeline.swift in Sources */,
|
D6109A11214607D500432DC2 /* Timeline.swift in Sources */,
|
||||||
D60E2F3324425374005F8713 /* AccountProtocol.swift in Sources */,
|
|
||||||
D61099E7214561FF00432DC2 /* Attachment.swift in Sources */,
|
D61099E7214561FF00432DC2 /* Attachment.swift in Sources */,
|
||||||
D60E2F3124424F1A005F8713 /* StatusProtocol.swift in Sources */,
|
|
||||||
D61099D02144B2D700432DC2 /* Method.swift in Sources */,
|
D61099D02144B2D700432DC2 /* Method.swift in Sources */,
|
||||||
D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */,
|
D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */,
|
||||||
D61099FB214569F600432DC2 /* Report.swift in Sources */,
|
D61099FB214569F600432DC2 /* Report.swift in Sources */,
|
||||||
|
@ -1674,6 +1658,7 @@
|
||||||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
||||||
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */,
|
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */,
|
||||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
||||||
|
D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */,
|
||||||
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
|
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
|
||||||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */,
|
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */,
|
||||||
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */,
|
D6945C3823AC739F005C403C /* InstanceTimelineViewController.swift in Sources */,
|
||||||
|
@ -1766,7 +1751,6 @@
|
||||||
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
||||||
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
||||||
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */,
|
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */,
|
||||||
D64D8CA92463B494006B0BAA /* CachedDictionary.swift in Sources */,
|
|
||||||
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
|
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
|
||||||
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */,
|
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */,
|
||||||
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
|
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
|
||||||
|
|
|
@ -92,12 +92,6 @@
|
||||||
ReferencedContainer = "container:Tusker.xcodeproj">
|
ReferencedContainer = "container:Tusker.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
<CommandLineArguments>
|
|
||||||
<CommandLineArgument
|
|
||||||
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
|
|
||||||
isEnabled = "YES">
|
|
||||||
</CommandLineArgument>
|
|
||||||
</CommandLineArguments>
|
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
|
|
@ -29,7 +29,9 @@ class FollowAccountActivity: AccountActivity {
|
||||||
|
|
||||||
let request = Account.follow(account.id)
|
let request = Account.follow(account.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case .failure(_) = response {
|
if case let .success(relationship, _) = response {
|
||||||
|
self.mastodonController.cache.add(relationship: relationship)
|
||||||
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
fatalError()
|
fatalError()
|
||||||
|
|
|
@ -29,7 +29,9 @@ class UnfollowAccountActivity: AccountActivity {
|
||||||
|
|
||||||
let request = Account.unfollow(account.id)
|
let request = Account.unfollow(account.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case .failure(_) = response {
|
if case let .success(relationship, _) = response {
|
||||||
|
self.mastodonController.cache.add(relationship: relationship)
|
||||||
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
fatalError()
|
fatalError()
|
||||||
|
|
|
@ -29,7 +29,7 @@ class BookmarkStatusActivity: StatusActivity {
|
||||||
let request = Status.bookmark(status)
|
let request = Status.bookmark(status)
|
||||||
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, incrementReferenceCount: false)
|
self.mastodonController.cache.add(status: status)
|
||||||
} 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)
|
let request = Status.pin(status)
|
||||||
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, incrementReferenceCount: false)
|
self.mastodonController.cache.add(status: status)
|
||||||
} else {
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
|
|
@ -26,10 +26,10 @@ class UnbookmarkStatusActivity: StatusActivity {
|
||||||
override func perform() {
|
override func perform() {
|
||||||
guard let status = status else { return }
|
guard let status = status else { return }
|
||||||
|
|
||||||
let request = Status.unbookmark(status.id)
|
let request = Status.unbookmark(status)
|
||||||
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, incrementReferenceCount: false)
|
self.mastodonController.cache.add(status: status)
|
||||||
} 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)
|
let request = Status.unpin(status)
|
||||||
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, incrementReferenceCount: false)
|
self.mastodonController.cache.add(status: status)
|
||||||
} else {
|
} else {
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
//
|
|
||||||
// CachedDictionary.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 5/6/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class CachedDictionary<Value> {
|
|
||||||
private let name: String
|
|
||||||
private var dict = [String: Value]()
|
|
||||||
private let queue: DispatchQueue
|
|
||||||
|
|
||||||
init(name: String) {
|
|
||||||
self.name = name
|
|
||||||
self.queue = DispatchQueue(label: "CachedDictionary (\(name)) Coordinator", attributes: .concurrent)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript(key: String) -> Value? {
|
|
||||||
get {
|
|
||||||
var result: Value? = nil
|
|
||||||
queue.sync {
|
|
||||||
result = dict[key]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
set(value) {
|
|
||||||
queue.async(flags: .barrier) {
|
|
||||||
self.dict[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,6 +30,8 @@ class MastodonController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private(set) lazy var cache = MastodonCache(mastodonController: self)
|
||||||
|
|
||||||
private(set) lazy var persistentContainer = MastodonCachePersistentStore(for: self)
|
private(set) lazy var persistentContainer = MastodonCachePersistentStore(for: self)
|
||||||
|
|
||||||
let instanceURL: URL
|
let instanceURL: URL
|
||||||
|
@ -82,7 +84,7 @@ class MastodonController {
|
||||||
run(request) { response in
|
run(request) { response in
|
||||||
guard case let .success(account, _) = response else { fatalError() }
|
guard case let .success(account, _) = response else { fatalError() }
|
||||||
self.account = account
|
self.account = account
|
||||||
self.persistentContainer.addOrUpdate(account: account)
|
self.cache.add(account: account)
|
||||||
completion?(account)
|
completion?(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import CoreData
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
@objc(AccountMO)
|
@objc(AccountMO)
|
||||||
public final class AccountMO: NSManagedObject, AccountProtocol {
|
public final class AccountMO: NSManagedObject {
|
||||||
|
|
||||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<AccountMO> {
|
@nonobjc public class func fetchRequest() -> NSFetchRequest<AccountMO> {
|
||||||
return NSFetchRequest<AccountMO>(entityName: "Account")
|
return NSFetchRequest<AccountMO>(entityName: "Account")
|
||||||
|
@ -20,7 +20,7 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
|
||||||
|
|
||||||
@NSManaged public var acct: String
|
@NSManaged public var acct: String
|
||||||
@NSManaged public var avatar: URL
|
@NSManaged public var avatar: URL
|
||||||
@NSManaged public var botCD: Bool
|
@NSManaged public var bot: Bool
|
||||||
@NSManaged public var createdAt: Date
|
@NSManaged public var createdAt: Date
|
||||||
@NSManaged public var displayName: String
|
@NSManaged public var displayName: String
|
||||||
@NSManaged private var emojisData: Data?
|
@NSManaged private var emojisData: Data?
|
||||||
|
@ -30,7 +30,7 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
|
||||||
@NSManaged public var header: URL
|
@NSManaged public var header: URL
|
||||||
@NSManaged public var id: String
|
@NSManaged public var id: String
|
||||||
@NSManaged public var locked: Bool
|
@NSManaged public var locked: Bool
|
||||||
@NSManaged public var movedCD: Bool
|
@NSManaged public var moved: Bool
|
||||||
@NSManaged public var note: String
|
@NSManaged public var note: String
|
||||||
@NSManaged public var statusesCount: Int
|
@NSManaged public var statusesCount: Int
|
||||||
@NSManaged public var url: URL
|
@NSManaged public var url: URL
|
||||||
|
@ -38,13 +38,10 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
|
||||||
@NSManaged public var movedTo: AccountMO?
|
@NSManaged public var movedTo: AccountMO?
|
||||||
|
|
||||||
@LazilyDecoding(arrayFrom: \AccountMO.emojisData)
|
@LazilyDecoding(arrayFrom: \AccountMO.emojisData)
|
||||||
public var emojis: [Emoji]
|
var emojis: [Emoji]
|
||||||
|
|
||||||
@LazilyDecoding(arrayFrom: \AccountMO.fieldsData)
|
@LazilyDecoding(arrayFrom: \AccountMO.fieldsData)
|
||||||
public var fields: [Pachyderm.Account.Field]
|
var fields: [Account.Field]
|
||||||
|
|
||||||
public var bot: Bool? { botCD }
|
|
||||||
public var moved: Bool? { movedCD }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,17 +59,17 @@ extension AccountMO {
|
||||||
|
|
||||||
self.acct = account.acct
|
self.acct = account.acct
|
||||||
self.avatar = account.avatarStatic // we don't animate avatars
|
self.avatar = account.avatarStatic // we don't animate avatars
|
||||||
self.botCD = account.bot ?? false
|
self.bot = account.bot ?? false
|
||||||
self.createdAt = account.createdAt
|
self.createdAt = account.createdAt
|
||||||
self.displayName = account.displayName
|
self.displayName = account.displayName
|
||||||
self.emojis = account.emojis
|
self.emojis = account.emojis
|
||||||
self.fields = account.fields
|
self.fields = account.fields ?? []
|
||||||
self.followersCount = account.followersCount
|
self.followersCount = account.followersCount
|
||||||
self.followingCount = account.followingCount
|
self.followingCount = account.followingCount
|
||||||
self.header = account.headerStatic // we don't animate headers
|
self.header = account.headerStatic // we don't animate headers
|
||||||
self.id = account.id
|
self.id = account.id
|
||||||
self.locked = account.locked
|
self.locked = account.locked
|
||||||
self.movedCD = account.moved ?? false
|
self.moved = account.moved ?? false
|
||||||
self.note = account.note
|
self.note = account.note
|
||||||
self.statusesCount = account.statusesCount
|
self.statusesCount = account.statusesCount
|
||||||
self.url = account.url
|
self.url = account.url
|
||||||
|
|
|
@ -9,15 +9,11 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Combine
|
|
||||||
|
|
||||||
class MastodonCachePersistentStore: NSPersistentContainer {
|
class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
|
|
||||||
private(set) lazy var backgroundContext = newBackgroundContext()
|
private(set) lazy var backgroundContext = newBackgroundContext()
|
||||||
|
|
||||||
let statusSubject = PassthroughSubject<String, Never>()
|
|
||||||
let accountSubject = PassthroughSubject<String, Never>()
|
|
||||||
|
|
||||||
init(for controller: MastodonController) {
|
init(for controller: MastodonController) {
|
||||||
let url = Bundle.main.url(forResource: "Tusker", withExtension: "momd")!
|
let url = Bundle.main.url(forResource: "Tusker", withExtension: "momd")!
|
||||||
let model = NSManagedObjectModel(contentsOf: url)!
|
let model = NSManagedObjectModel(contentsOf: url)!
|
||||||
|
@ -41,31 +37,27 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
private func upsert(status: Status, incrementReferenceCount: Bool) {
|
||||||
private func upsert(status: Status, incrementReferenceCount: Bool) -> StatusMO {
|
|
||||||
if let statusMO = self.status(for: status.id, in: self.backgroundContext) {
|
if let statusMO = self.status(for: status.id, in: self.backgroundContext) {
|
||||||
statusMO.updateFrom(apiStatus: status, container: self)
|
statusMO.updateFrom(apiStatus: status, container: self)
|
||||||
if incrementReferenceCount {
|
if incrementReferenceCount {
|
||||||
statusMO.incrementReferenceCount()
|
statusMO.incrementReferenceCount()
|
||||||
}
|
}
|
||||||
return statusMO
|
|
||||||
} else {
|
} else {
|
||||||
let statusMO = StatusMO(apiStatus: status, container: self, context: self.backgroundContext)
|
let statusMO = StatusMO(apiStatus: status, container: self, context: self.backgroundContext)
|
||||||
if incrementReferenceCount {
|
if incrementReferenceCount {
|
||||||
statusMO.incrementReferenceCount()
|
statusMO.incrementReferenceCount()
|
||||||
}
|
}
|
||||||
return statusMO
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOrUpdate(status: Status, incrementReferenceCount: Bool, completion: ((StatusMO) -> Void)? = nil) {
|
func addOrUpdate(status: Status, incrementReferenceCount: Bool, completion: (() -> Void)?) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
let statusMO = self.upsert(status: status, incrementReferenceCount: incrementReferenceCount)
|
self.upsert(status: status, incrementReferenceCount: incrementReferenceCount)
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
completion?(statusMO)
|
completion?()
|
||||||
self.statusSubject.send(status.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +68,6 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
completion?()
|
completion?()
|
||||||
statuses.forEach { self.statusSubject.send($0.id) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,50 +83,31 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
private func upsert(account: Account) {
|
||||||
private func upsert(account: Account) -> AccountMO {
|
|
||||||
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
|
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
|
||||||
accountMO.updateFrom(apiAccount: account, container: self)
|
accountMO.updateFrom(apiAccount: account, container: self)
|
||||||
return accountMO
|
|
||||||
} else {
|
} else {
|
||||||
return AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
_ = AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
|
func addOrUpdate(account: Account, completion: (() -> Void)?) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
let accountMO = self.upsert(account: account)
|
self.upsert(account: account)
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
completion?(accountMO)
|
completion?()
|
||||||
self.accountSubject.send(account.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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:))
|
||||||
if self.backgroundContext.hasChanges {
|
if self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
completion?()
|
completion?()
|
||||||
accounts.forEach { self.accountSubject.send($0.id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addAll(notifications: [Pachyderm.Notification], completion: (() -> Void)? = nil) {
|
|
||||||
backgroundContext.perform {
|
|
||||||
let statuses = notifications.compactMap { $0.status }
|
|
||||||
let accounts = notifications.map { $0.account }
|
|
||||||
statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true) }
|
|
||||||
accounts.forEach { self.upsert(account: $0) }
|
|
||||||
if self.backgroundContext.hasChanges {
|
|
||||||
try! self.backgroundContext.save()
|
|
||||||
}
|
|
||||||
completion?()
|
|
||||||
statuses.forEach { self.statusSubject.send($0.id) }
|
|
||||||
accounts.forEach { self.accountSubject.send($0.id) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,27 +12,27 @@ import CoreData
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
@objc(StatusMO)
|
@objc(StatusMO)
|
||||||
public final class StatusMO: NSManagedObject, StatusProtocol {
|
public final class StatusMO: NSManagedObject {
|
||||||
|
|
||||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<StatusMO> {
|
@nonobjc public class func fetchRequest() -> NSFetchRequest<StatusMO> {
|
||||||
return NSFetchRequest<StatusMO>(entityName: "Status")
|
return NSFetchRequest<StatusMO>(entityName: "Status")
|
||||||
}
|
}
|
||||||
|
|
||||||
@NSManaged public var applicationName: String?
|
@NSManaged public var application: String?
|
||||||
@NSManaged private var attachmentsData: Data?
|
@NSManaged private var attachmentsData: Data?
|
||||||
@NSManaged private var bookmarkedInternal: Bool
|
@NSManaged public var bookmarked: Bool
|
||||||
@NSManaged public var content: String
|
@NSManaged public var content: String
|
||||||
@NSManaged public var createdAt: Date
|
@NSManaged public var createdAt: Date
|
||||||
@NSManaged private var emojisData: Data?
|
@NSManaged public var emojisData: Data?
|
||||||
@NSManaged public var favourited: Bool
|
@NSManaged public var favourited: Bool
|
||||||
@NSManaged public var favouritesCount: Int
|
@NSManaged public var favouritesCount: Int
|
||||||
@NSManaged private var hashtagsData: Data?
|
@NSManaged public var hashtagsData: Data?
|
||||||
@NSManaged public var id: String
|
@NSManaged public var id: String
|
||||||
@NSManaged public var inReplyToAccountID: String?
|
@NSManaged public var inReplyToAccountID: String?
|
||||||
@NSManaged public var inReplyToID: String?
|
@NSManaged public var inReplyToID: String?
|
||||||
@NSManaged private var mentionsData: Data?
|
@NSManaged private var mentionsData: Data?
|
||||||
@NSManaged public var muted: Bool
|
@NSManaged public var muted: Bool
|
||||||
@NSManaged private var pinnedInternal: Bool
|
@NSManaged public var pinned: 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 referenceCount: Int
|
||||||
|
@ -45,21 +45,18 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
|
||||||
@NSManaged public var reblog: StatusMO?
|
@NSManaged public var reblog: StatusMO?
|
||||||
|
|
||||||
@LazilyDecoding(arrayFrom: \StatusMO.attachmentsData)
|
@LazilyDecoding(arrayFrom: \StatusMO.attachmentsData)
|
||||||
public var attachments: [Attachment]
|
var attachments: [Attachment]
|
||||||
|
|
||||||
@LazilyDecoding(arrayFrom: \StatusMO.emojisData)
|
@LazilyDecoding(arrayFrom: \StatusMO.emojisData)
|
||||||
public var emojis: [Emoji]
|
var emojis: [Emoji]
|
||||||
|
|
||||||
@LazilyDecoding(arrayFrom: \StatusMO.hashtagsData)
|
@LazilyDecoding(arrayFrom: \StatusMO.hashtagsData)
|
||||||
public var hashtags: [Hashtag]
|
var hashtags: [Hashtag]
|
||||||
|
|
||||||
@LazilyDecoding(arrayFrom: \StatusMO.mentionsData)
|
@LazilyDecoding(arrayFrom: \StatusMO.mentionsData)
|
||||||
public var mentions: [Mention]
|
var mentions: [Mention]
|
||||||
|
|
||||||
public var pinned: Bool? { pinnedInternal }
|
var visibility: Status.Visibility {
|
||||||
public var bookmarked: Bool? { bookmarkedInternal }
|
|
||||||
|
|
||||||
public var visibility: Pachyderm.Status.Visibility {
|
|
||||||
get {
|
get {
|
||||||
Pachyderm.Status.Visibility(rawValue: visibilityString) ?? .public
|
Pachyderm.Status.Visibility(rawValue: visibilityString) ?? .public
|
||||||
}
|
}
|
||||||
|
@ -100,13 +97,13 @@ extension StatusMO {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.applicationName = status.application?.name
|
self.application = status.application?.name
|
||||||
self.attachments = status.attachments
|
self.attachments = status.attachments
|
||||||
self.bookmarkedInternal = status.bookmarked ?? false
|
self.bookmarked = status.bookmarked ?? false
|
||||||
self.content = status.content
|
self.content = status.content
|
||||||
self.createdAt = status.createdAt
|
self.createdAt = status.createdAt
|
||||||
self.emojis = status.emojis
|
self.emojis = status.emojis
|
||||||
self.favourited = status.favourited
|
self.favourited = status.favourited ?? false
|
||||||
self.favouritesCount = status.favouritesCount
|
self.favouritesCount = status.favouritesCount
|
||||||
self.hashtags = status.hashtags
|
self.hashtags = status.hashtags
|
||||||
self.inReplyToAccountID = status.inReplyToAccountID
|
self.inReplyToAccountID = status.inReplyToAccountID
|
||||||
|
@ -114,8 +111,8 @@ extension StatusMO {
|
||||||
self.id = status.id
|
self.id = status.id
|
||||||
self.mentions = status.mentions
|
self.mentions = status.mentions
|
||||||
self.muted = status.muted ?? false
|
self.muted = status.muted ?? false
|
||||||
self.pinnedInternal = status.pinned ?? false
|
self.pinned = status.pinned ?? false
|
||||||
self.reblogged = status.reblogged
|
self.reblogged = status.reblogged ?? false
|
||||||
self.reblogsCount = status.reblogsCount
|
self.reblogsCount = status.reblogsCount
|
||||||
self.sensitive = status.sensitive
|
self.sensitive = status.sensitive
|
||||||
self.spoilerText = status.spoilerText
|
self.spoilerText = status.spoilerText
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<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" attributeType="URI"/>
|
<attribute name="avatar" attributeType="URI"/>
|
||||||
<attribute name="botCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="bot" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="displayName" attributeType="String"/>
|
<attribute name="displayName" attributeType="String"/>
|
||||||
<attribute name="emojisData" attributeType="Binary"/>
|
<attribute name="emojisData" attributeType="Binary"/>
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
<attribute name="header" attributeType="URI"/>
|
<attribute name="header" attributeType="URI"/>
|
||||||
<attribute name="id" attributeType="String"/>
|
<attribute name="id" attributeType="String"/>
|
||||||
<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="moved" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="note" attributeType="String"/>
|
<attribute name="note" attributeType="String"/>
|
||||||
<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"/>
|
||||||
|
@ -26,13 +26,13 @@
|
||||||
</uniquenessConstraints>
|
</uniquenessConstraints>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="Status" representedClassName="StatusMO" syncable="YES">
|
<entity name="Status" representedClassName="StatusMO" syncable="YES">
|
||||||
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
<attribute name="application" optional="YES" attributeType="String"/>
|
||||||
<attribute name="attachmentsData" attributeType="Binary"/>
|
<attribute name="attachmentsData" attributeType="Binary"/>
|
||||||
<attribute name="bookmarkedInternal" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="bookmarked" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="content" attributeType="String"/>
|
<attribute name="content" attributeType="String"/>
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="emojisData" attributeType="Binary" customClassName="[Data]"/>
|
<attribute name="emojisData" attributeType="Binary" customClassName="[Data]"/>
|
||||||
<attribute name="favourited" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="favourited" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="favouritesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="favouritesCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="hashtagsData" attributeType="Binary"/>
|
<attribute name="hashtagsData" attributeType="Binary"/>
|
||||||
<attribute name="id" attributeType="String"/>
|
<attribute name="id" attributeType="String"/>
|
||||||
|
@ -40,8 +40,8 @@
|
||||||
<attribute name="inReplyToID" optional="YES" attributeType="String"/>
|
<attribute name="inReplyToID" optional="YES" attributeType="String"/>
|
||||||
<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"/>
|
||||||
<attribute name="pinnedInternal" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="pinned" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
<attribute name="reblogged" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="reblogged" optional="YES" 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="referenceCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
@ -49,7 +49,6 @@
|
||||||
<attribute name="uri" attributeType="String"/>
|
<attribute name="uri" attributeType="String"/>
|
||||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||||
<attribute name="visibilityString" attributeType="String"/>
|
<attribute name="visibilityString" attributeType="String"/>
|
||||||
|
|
||||||
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="Account"/>
|
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="Account"/>
|
||||||
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/>
|
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status"/>
|
||||||
<uniquenessConstraints>
|
<uniquenessConstraints>
|
||||||
|
|
|
@ -13,7 +13,7 @@ private let encoder = PropertyListEncoder()
|
||||||
|
|
||||||
// todo: invalidate cache on underlying data change using KVO?
|
// todo: invalidate cache on underlying data change using KVO?
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public struct LazilyDecoding<Enclosing, Value: Codable> {
|
struct LazilyDecoding<Enclosing, Value: Codable> {
|
||||||
|
|
||||||
private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?>
|
private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?>
|
||||||
private let fallback: Value
|
private let fallback: Value
|
||||||
|
@ -24,12 +24,12 @@ public struct LazilyDecoding<Enclosing, Value: Codable> {
|
||||||
self.fallback = fallback
|
self.fallback = fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
public var wrappedValue: Value {
|
var wrappedValue: Value {
|
||||||
get { fatalError("called LazilyDecoding wrappedValue getter") }
|
get { fatalError("called LazilyDecoding wrappedValue getter") }
|
||||||
set { fatalError("called LazilyDecoding wrappedValue setter") }
|
set { fatalError("called LazilyDecoding wrappedValue setter") }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath<Enclosing, Value>, storage storageKeyPath: ReferenceWritableKeyPath<Enclosing, Self>) -> Value {
|
static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath<Enclosing, Value>, storage storageKeyPath: ReferenceWritableKeyPath<Enclosing, Self>) -> Value {
|
||||||
get {
|
get {
|
||||||
var wrapper = instance[keyPath: storageKeyPath]
|
var wrapper = instance[keyPath: storageKeyPath]
|
||||||
if let value = wrapper.value {
|
if let value = wrapper.value {
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
//
|
||||||
|
// StatusCache.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/17/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import Pachyderm
|
||||||
|
|
||||||
|
class MastodonCache {
|
||||||
|
|
||||||
|
private var statuses = CachedDictionary<Status>(name: "Statuses")
|
||||||
|
private var accounts = CachedDictionary<Account>(name: "Accounts")
|
||||||
|
private var relationships = CachedDictionary<Relationship>(name: "Relationships")
|
||||||
|
private var notifications = CachedDictionary<Pachyderm.Notification>(name: "Notifications")
|
||||||
|
|
||||||
|
let statusSubject = PassthroughSubject<Status, Never>()
|
||||||
|
let accountSubject = PassthroughSubject<Account, Never>()
|
||||||
|
|
||||||
|
weak var mastodonController: MastodonController?
|
||||||
|
|
||||||
|
init(mastodonController: MastodonController) {
|
||||||
|
self.mastodonController = mastodonController
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Statuses
|
||||||
|
func status(for id: String) -> Status? {
|
||||||
|
return statuses[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(status: Status, for id: String) {
|
||||||
|
statuses[id] = status
|
||||||
|
add(account: status.account)
|
||||||
|
if let reblog = status.reblog {
|
||||||
|
add(status: reblog)
|
||||||
|
add(account: reblog.account)
|
||||||
|
}
|
||||||
|
|
||||||
|
statusSubject.send(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func status(for id: String, completion: @escaping (Status?) -> Void) {
|
||||||
|
guard let mastodonController = mastodonController else {
|
||||||
|
fatalError("The MastodonController for this cache has been deinitialized, so this cache should no longer exist. Are you storing a strong reference to it?")
|
||||||
|
}
|
||||||
|
let request = Client.getStatus(id: id)
|
||||||
|
mastodonController.run(request) { response in
|
||||||
|
guard case let .success(status, _) = response else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.set(status: status, for: id)
|
||||||
|
completion(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(status: Status) {
|
||||||
|
set(status: status, for: status.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAll(statuses: [Status]) {
|
||||||
|
statuses.forEach(add)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Accounts
|
||||||
|
func account(for id: String) -> Account? {
|
||||||
|
return accounts[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(account: Account, for id: String) {
|
||||||
|
accounts[id] = account
|
||||||
|
accountSubject.send(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func account(for id: String, completion: @escaping (Account?) -> Void) {
|
||||||
|
guard let mastodonController = mastodonController else {
|
||||||
|
fatalError("The MastodonController for this cache has been deinitialized, so this cache should no longer exist. Are you storing a strong reference to it?")
|
||||||
|
}
|
||||||
|
let request = Client.getAccount(id: id)
|
||||||
|
mastodonController.run(request) { response in
|
||||||
|
guard case let .success(account, _) = response else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.set(account: account, for: account.id)
|
||||||
|
completion(account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(account: Account) {
|
||||||
|
set(account: account, for: account.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAll(accounts: [Account]) {
|
||||||
|
accounts.forEach(add)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Relationships
|
||||||
|
func relationship(for id: String) -> Relationship? {
|
||||||
|
return relationships[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(relationship: Relationship, id: String) {
|
||||||
|
relationships[id] = relationship
|
||||||
|
}
|
||||||
|
|
||||||
|
func relationship(for id: String, completion: @escaping (Relationship?) -> Void) {
|
||||||
|
guard let mastodonController = mastodonController else {
|
||||||
|
fatalError("The MastodonController for this cache has been deinitialized, so this cache should no longer exist. Are you storing a strong reference to it?")
|
||||||
|
}
|
||||||
|
let request = Client.getRelationships(accounts: [id])
|
||||||
|
mastodonController.run(request) { response in
|
||||||
|
guard case let .success(relationships, _) = response,
|
||||||
|
let relationship = relationships.first else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.set(relationship: relationship, id: relationship.id)
|
||||||
|
completion(relationship)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(relationship: Relationship) {
|
||||||
|
set(relationship: relationship, id: relationship.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAll(relationships: [Relationship]) {
|
||||||
|
relationships.forEach(add)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Notifications
|
||||||
|
func notification(for id: String) -> Pachyderm.Notification? {
|
||||||
|
return notifications[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(notification: Pachyderm.Notification, id: String) {
|
||||||
|
notifications[id] = notification
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(notification: Pachyderm.Notification) {
|
||||||
|
set(notification: notification, id: notification.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAll(notifications: [Pachyderm.Notification]) {
|
||||||
|
notifications.forEach(add)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class CachedDictionary<Value> {
|
||||||
|
private let name: String
|
||||||
|
private var dict = [String: Value]()
|
||||||
|
private let queue: DispatchQueue
|
||||||
|
|
||||||
|
init(name: String) {
|
||||||
|
self.name = name
|
||||||
|
self.queue = DispatchQueue(label: "CachedDictionary (\(name)) Coordinator", attributes: .concurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(key: String) -> Value? {
|
||||||
|
get {
|
||||||
|
var result: Value? = nil
|
||||||
|
queue.sync {
|
||||||
|
result = dict[key]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
queue.async(flags: .barrier) {
|
||||||
|
self.dict[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
let request = Client.getBookmarks()
|
let request = Client.getBookmarks()
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
guard case let .success(statuses, pagination) = response else { fatalError() }
|
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||||
self.mastodonController.persistentContainer.addAll(statuses: statuses)
|
self.mastodonController.cache.addAll(statuses: statuses)
|
||||||
self.statuses.append(contentsOf: statuses.map { ($0.id, .unknown) })
|
self.statuses.append(contentsOf: statuses.map { ($0.id, .unknown) })
|
||||||
self.newer = pagination?.newer
|
self.newer = pagination?.newer
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
|
@ -87,7 +87,7 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
self.mastodonController.persistentContainer.addAll(statuses: newStatuses)
|
self.mastodonController.cache.addAll(statuses: newStatuses)
|
||||||
let newIndexPaths = (self.statuses.count..<(self.statuses.count + newStatuses.count)).map {
|
let newIndexPaths = (self.statuses.count..<(self.statuses.count + newStatuses.count)).map {
|
||||||
IndexPath(row: $0, section: 0)
|
IndexPath(row: $0, section: 0)
|
||||||
}
|
}
|
||||||
|
@ -112,15 +112,15 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
let cellConfig = (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.trailingSwipeActionsConfiguration()
|
let cellConfig = (tableView.cellForRow(at: indexPath) as? TableViewSwipeActionProvider)?.trailingSwipeActionsConfiguration()
|
||||||
|
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statuses[indexPath.row].id) else {
|
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else {
|
||||||
return cellConfig
|
return cellConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
let unbookmarkAction = UIContextualAction(style: .destructive, title: NSLocalizedString("Unbookmark", comment: "unbookmark action title")) { (action, view, completion) in
|
let unbookmarkAction = UIContextualAction(style: .destructive, title: NSLocalizedString("Unbookmark", comment: "unbookmark action title")) { (action, view, completion) in
|
||||||
let request = Status.unbookmark(status.id)
|
let request = Status.unbookmark(status)
|
||||||
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, incrementReferenceCount: false)
|
self.mastodonController.cache.add(status: newStatus)
|
||||||
self.statuses.remove(at: indexPath.row)
|
self.statuses.remove(at: indexPath.row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,13 +138,13 @@ class BookmarksTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func getSuggestedContextMenuActions(tableView: UITableView, indexPath: IndexPath, point: CGPoint) -> [UIAction] {
|
override func getSuggestedContextMenuActions(tableView: UITableView, indexPath: IndexPath, point: CGPoint) -> [UIAction] {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statuses[indexPath.row].id) else { return [] }
|
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { return [] }
|
||||||
return [
|
return [
|
||||||
UIAction(title: NSLocalizedString("Unbookmark", comment: "unbookmark action title"), image: UIImage(systemName: "bookmark.fill"), identifier: .init("unbookmark"), discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
|
UIAction(title: NSLocalizedString("Unbookmark", comment: "unbookmark action title"), image: UIImage(systemName: "bookmark.fill"), identifier: .init("unbookmark"), discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
|
||||||
let request = Status.unbookmark(status.id)
|
let request = Status.unbookmark(status)
|
||||||
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, incrementReferenceCount: false)
|
self.mastodonController.cache.add(status: newStatus)
|
||||||
self.statuses.remove(at: indexPath.row)
|
self.statuses.remove(at: indexPath.row)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -165,7 +165,7 @@ extension BookmarksTableViewController: StatusTableViewCellDelegate {
|
||||||
extension BookmarksTableViewController: UITableViewDataSourcePrefetching {
|
extension BookmarksTableViewController: UITableViewDataSourcePrefetching {
|
||||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||||
for indexPath in indexPaths {
|
for indexPath in indexPaths {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statuses[indexPath.row].id) else { continue }
|
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||||
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||||
for attachment in status.attachments where attachment.kind == .image {
|
for attachment in status.attachments where attachment.kind == .image {
|
||||||
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
||||||
|
@ -175,7 +175,7 @@ extension BookmarksTableViewController: UITableViewDataSourcePrefetching {
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||||
for indexPath in indexPaths {
|
for indexPath in indexPaths {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statuses[indexPath.row].id) else { continue }
|
guard let status = mastodonController.cache.status(for: statuses[indexPath.row].id) else { continue }
|
||||||
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
||||||
for attachment in status.attachments where attachment.kind == .image {
|
for attachment in status.attachments where attachment.kind == .image {
|
||||||
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
||||||
|
|
|
@ -72,7 +72,7 @@ class ComposeViewController: UIViewController {
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
|
||||||
self.inReplyToID = inReplyToID
|
self.inReplyToID = inReplyToID
|
||||||
if let inReplyToID = inReplyToID, let inReplyTo = mastodonController.persistentContainer.status(for: inReplyToID) {
|
if let inReplyToID = inReplyToID, let inReplyTo = mastodonController.cache.status(for: inReplyToID) {
|
||||||
accountsToMention = [inReplyTo.account.acct] + inReplyTo.mentions.map { $0.acct }
|
accountsToMention = [inReplyTo.account.acct] + inReplyTo.mentions.map { $0.acct }
|
||||||
} else {
|
} else {
|
||||||
accountsToMention = []
|
accountsToMention = []
|
||||||
|
@ -164,20 +164,17 @@ class ComposeViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let inReplyToID = inReplyToID {
|
if let inReplyToID = inReplyToID {
|
||||||
if let status = mastodonController.persistentContainer.status(for: inReplyToID) {
|
if let status = mastodonController.cache.status(for: inReplyToID) {
|
||||||
updateInReplyTo(inReplyTo: status)
|
updateInReplyTo(inReplyTo: status)
|
||||||
} else {
|
} else {
|
||||||
let loadingVC = LoadingViewController()
|
let loadingVC = LoadingViewController()
|
||||||
embedChild(loadingVC)
|
embedChild(loadingVC)
|
||||||
|
|
||||||
let request = Client.getStatus(id: inReplyToID)
|
mastodonController.cache.status(for: inReplyToID) { (status) in
|
||||||
mastodonController.run(request) { (response) in
|
guard let status = status else { return }
|
||||||
guard case let .success(status, _) = response else { return }
|
DispatchQueue.main.async {
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: true) { (status) in
|
self.updateInReplyTo(inReplyTo: status)
|
||||||
DispatchQueue.main.async {
|
loadingVC.removeViewAndController()
|
||||||
self.updateInReplyTo(inReplyTo: status)
|
|
||||||
loadingVC.removeViewAndController()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,7 +186,7 @@ class ComposeViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateInReplyTo(inReplyTo: StatusMO) {
|
func updateInReplyTo(inReplyTo: Status) {
|
||||||
visibility = inReplyTo.visibility
|
visibility = inReplyTo.visibility
|
||||||
if Preferences.shared.contentWarningCopyMode == .doNotCopy {
|
if Preferences.shared.contentWarningCopyMode == .doNotCopy {
|
||||||
contentWarningEnabled = false
|
contentWarningEnabled = false
|
||||||
|
@ -473,7 +470,7 @@ class ComposeViewController: UIViewController {
|
||||||
self.mastodonController.run(request) { (response) in
|
self.mastodonController.run(request) { (response) in
|
||||||
guard case let .success(status, _) = response else { fatalError() }
|
guard case let .success(status, _) = response else { fatalError() }
|
||||||
self.postedStatus = status
|
self.postedStatus = status
|
||||||
// self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: true)
|
self.mastodonController.cache.add(status: status)
|
||||||
|
|
||||||
if let draft = self.currentDraft {
|
if let draft = self.currentDraft {
|
||||||
DraftsManager.shared.remove(draft)
|
DraftsManager.shared.remove(draft)
|
||||||
|
@ -484,8 +481,8 @@ class ComposeViewController: UIViewController {
|
||||||
self.dismiss(animated: true)
|
self.dismiss(animated: true)
|
||||||
|
|
||||||
// todo: this doesn't work
|
// todo: this doesn't work
|
||||||
// let conversationVC = ConversationTableViewController(for: status.id, mastodonController: self.mastodonController)
|
let conversationVC = ConversationTableViewController(for: status.id, mastodonController: self.mastodonController)
|
||||||
// self.show(conversationVC, sender: self)
|
self.show(conversationVC, sender: self)
|
||||||
|
|
||||||
self.xcbSession?.complete(with: .success, additionalData: [
|
self.xcbSession?.complete(with: .success, additionalData: [
|
||||||
"statusURL": status.url?.absoluteString,
|
"statusURL": status.url?.absoluteString,
|
||||||
|
|
|
@ -80,7 +80,7 @@ class EditListAccountsViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
self.nextRange = pagination?.older
|
self.nextRange = pagination?.older
|
||||||
|
|
||||||
self.mastodonController.persistentContainer.addAll(accounts: accounts)
|
self.mastodonController.cache.addAll(accounts: accounts)
|
||||||
|
|
||||||
var snapshot = self.dataSource.snapshot()
|
var snapshot = self.dataSource.snapshot()
|
||||||
snapshot.deleteSections([.accounts])
|
snapshot.deleteSections([.accounts])
|
||||||
|
|
|
@ -63,13 +63,15 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
self.groups.append(contentsOf: groups)
|
self.groups.append(contentsOf: groups)
|
||||||
|
|
||||||
|
self.mastodonController.cache.addAll(notifications: notifications)
|
||||||
|
self.mastodonController.cache.addAll(statuses: notifications.compactMap { $0.status })
|
||||||
|
self.mastodonController.cache.addAll(accounts: notifications.map { $0.account })
|
||||||
|
|
||||||
self.newer = pagination?.newer
|
self.newer = pagination?.newer
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
|
|
||||||
self.mastodonController.persistentContainer.addAll(notifications: notifications) {
|
DispatchQueue.main.async {
|
||||||
DispatchQueue.main.async {
|
self.tableView.reloadData()
|
||||||
self.tableView.reloadData()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +92,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
switch group.kind {
|
switch group.kind {
|
||||||
case .mention:
|
case .mention:
|
||||||
guard let notification = group.notifications.first,
|
guard let notification = mastodonController.cache.notification(for: group.notificationIDs.first!),
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else {
|
let cell = tableView.dequeueReusableCell(withIdentifier: statusCell, for: indexPath) as? TimelineStatusTableViewCell else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
@ -111,7 +113,7 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
case .followRequest:
|
case .followRequest:
|
||||||
guard let notification = group.notifications.first,
|
guard let notification = mastodonController.cache.notification(for: group.notificationIDs.first!),
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: followRequestCell, for: indexPath) as? FollowRequestNotificationTableViewCell else { fatalError() }
|
let cell = tableView.dequeueReusableCell(withIdentifier: followRequestCell, for: indexPath) as? FollowRequestNotificationTableViewCell else { fatalError() }
|
||||||
cell.delegate = self
|
cell.delegate = self
|
||||||
cell.updateUI(notification: notification)
|
cell.updateUI(notification: notification)
|
||||||
|
@ -141,13 +143,15 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
self.groups.append(contentsOf: groups)
|
self.groups.append(contentsOf: groups)
|
||||||
|
|
||||||
|
self.mastodonController.cache.addAll(notifications: newNotifications)
|
||||||
|
self.mastodonController.cache.addAll(statuses: newNotifications.compactMap { $0.status })
|
||||||
|
self.mastodonController.cache.addAll(accounts: newNotifications.map { $0.account })
|
||||||
|
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
|
|
||||||
self.mastodonController.persistentContainer.addAll(notifications: newNotifications) {
|
DispatchQueue.main.async {
|
||||||
DispatchQueue.main.async {
|
UIView.performWithoutAnimation {
|
||||||
UIView.performWithoutAnimation {
|
self.tableView.insertRows(at: newIndexPaths, with: .automatic)
|
||||||
self.tableView.insertRows(at: newIndexPaths, with: .automatic)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,8 +195,8 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
func dismissNotificationsInGroup(at indexPath: IndexPath, completion: (() -> Void)? = nil) {
|
func dismissNotificationsInGroup(at indexPath: IndexPath, completion: (() -> Void)? = nil) {
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
groups[indexPath.row].notifications
|
groups[indexPath.row].notificationIDs
|
||||||
.map { Pachyderm.Notification.dismiss(id: $0.id) }
|
.map(Pachyderm.Notification.dismiss(id:))
|
||||||
.forEach { (request) in
|
.forEach { (request) in
|
||||||
group.enter()
|
group.enter()
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
|
@ -217,24 +221,26 @@ class NotificationsTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
self.groups.insert(contentsOf: groups, at: 0)
|
self.groups.insert(contentsOf: groups, at: 0)
|
||||||
|
|
||||||
|
self.mastodonController.cache.addAll(notifications: newNotifications)
|
||||||
|
self.mastodonController.cache.addAll(statuses: newNotifications.compactMap { $0.status })
|
||||||
|
self.mastodonController.cache.addAll(accounts: newNotifications.map { $0.account })
|
||||||
|
|
||||||
if let newer = pagination?.newer {
|
if let newer = pagination?.newer {
|
||||||
self.newer = newer
|
self.newer = newer
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mastodonController.persistentContainer.addAll(notifications: newNotifications) {
|
DispatchQueue.main.async {
|
||||||
DispatchQueue.main.async {
|
let newIndexPaths = (0..<groups.count).map {
|
||||||
let newIndexPaths = (0..<groups.count).map {
|
IndexPath(row: $0, section: 0)
|
||||||
IndexPath(row: $0, section: 0)
|
|
||||||
}
|
|
||||||
UIView.performWithoutAnimation {
|
|
||||||
self.tableView.insertRows(at: newIndexPaths, with: .automatic)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.refreshControl?.endRefreshing()
|
|
||||||
|
|
||||||
// maintain the current position in the list (don't scroll to top)
|
|
||||||
self.tableView.scrollToRow(at: IndexPath(row: newNotifications.count, section: 0), at: .top, animated: false)
|
|
||||||
}
|
}
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.tableView.insertRows(at: newIndexPaths, with: .automatic)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.refreshControl?.endRefreshing()
|
||||||
|
|
||||||
|
// maintain the current position in the list (don't scroll to top)
|
||||||
|
self.tableView.scrollToRow(at: IndexPath(row: newNotifications.count, section: 0), at: .top, animated: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,8 +259,8 @@ extension NotificationsTableViewController: StatusTableViewCellDelegate {
|
||||||
extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
||||||
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||||
for indexPath in indexPaths {
|
for indexPath in indexPaths {
|
||||||
for notification in groups[indexPath.row].notifications {
|
for notificationID in groups[indexPath.row].notificationIDs {
|
||||||
// todo: this account object could be stale
|
guard let notification = mastodonController.cache.notification(for: notificationID) else { continue }
|
||||||
_ = ImageCache.avatars.get(notification.account.avatar, completion: nil)
|
_ = ImageCache.avatars.get(notification.account.avatar, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,7 +268,8 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
|
||||||
for indexPath in indexPaths {
|
for indexPath in indexPaths {
|
||||||
for notification in groups[indexPath.row].notifications {
|
for notificationID in groups[indexPath.row].notificationIDs {
|
||||||
|
guard let notification = mastodonController.cache.notification(for: notificationID) else { continue }
|
||||||
ImageCache.avatars.cancelWithoutCallback(notification.account.avatar)
|
ImageCache.avatars.cancelWithoutCallback(notification.account.avatar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,9 +78,8 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
} else {
|
} else {
|
||||||
loadingVC = LoadingViewController()
|
loadingVC = LoadingViewController()
|
||||||
embedChild(loadingVC!)
|
embedChild(loadingVC!)
|
||||||
let request = Client.getAccount(id: accountID)
|
mastodonController.cache.account(for: accountID) { (account) in
|
||||||
mastodonController.run(request) { (response) in
|
guard let account = account else {
|
||||||
guard case let .success(account, _) = response else {
|
|
||||||
let alert = UIAlertController(title: "Something Went Wrong", message: "Couldn't load the selected account", preferredStyle: .alert)
|
let alert = UIAlertController(title: "Something Went Wrong", message: "Couldn't load the selected account", preferredStyle: .alert)
|
||||||
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
|
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
|
||||||
self.navigationController!.popViewController(animated: true)
|
self.navigationController!.popViewController(animated: true)
|
||||||
|
@ -90,7 +89,7 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(account: account) { (_) in
|
self.mastodonController.persistentContainer.addOrUpdate(account: account) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.updateAccountUI()
|
self.updateAccountUI()
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
|
@ -280,10 +279,11 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
|
||||||
func showMoreOptions(cell: ProfileHeaderTableViewCell) {
|
func showMoreOptions(cell: ProfileHeaderTableViewCell) {
|
||||||
let account = mastodonController.persistentContainer.account(for: accountID)!
|
let account = mastodonController.persistentContainer.account(for: accountID)!
|
||||||
|
|
||||||
let request = Client.getRelationships(accounts: [account.id])
|
mastodonController.cache.relationship(for: account.id) { [weak self] (relationship) in
|
||||||
mastodonController.run(request) { (response) in
|
guard let self = self else { return }
|
||||||
|
|
||||||
var customActivities: [UIActivity] = [OpenInSafariActivity()]
|
var customActivities: [UIActivity] = [OpenInSafariActivity()]
|
||||||
if case let .success(results, _) = response, let relationship = results.first {
|
if let relationship = relationship {
|
||||||
let toggleFollowActivity = relationship.following ? UnfollowAccountActivity() : FollowAccountActivity()
|
let toggleFollowActivity = relationship.following ? UnfollowAccountActivity() : FollowAccountActivity()
|
||||||
customActivities.insert(toggleFollowActivity, at: 0)
|
customActivities.insert(toggleFollowActivity, at: 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ class SearchResultsViewController: EnhancedTableViewController {
|
||||||
if self.onlySections.contains(.accounts) && !results.accounts.isEmpty {
|
if self.onlySections.contains(.accounts) && !results.accounts.isEmpty {
|
||||||
snapshot.appendSections([.accounts])
|
snapshot.appendSections([.accounts])
|
||||||
snapshot.appendItems(results.accounts.map { .account($0.id) }, toSection: .accounts)
|
snapshot.appendItems(results.accounts.map { .account($0.id) }, toSection: .accounts)
|
||||||
self.mastodonController.persistentContainer.addAll(accounts: results.accounts)
|
self.mastodonController.cache.addAll(accounts: results.accounts)
|
||||||
}
|
}
|
||||||
if self.onlySections.contains(.hashtags) && !results.hashtags.isEmpty {
|
if self.onlySections.contains(.hashtags) && !results.hashtags.isEmpty {
|
||||||
snapshot.appendSections([.hashtags])
|
snapshot.appendSections([.hashtags])
|
||||||
|
@ -145,8 +145,8 @@ class SearchResultsViewController: EnhancedTableViewController {
|
||||||
if self.onlySections.contains(.statuses) && !results.statuses.isEmpty {
|
if self.onlySections.contains(.statuses) && !results.statuses.isEmpty {
|
||||||
snapshot.appendSections([.statuses])
|
snapshot.appendSections([.statuses])
|
||||||
snapshot.appendItems(results.statuses.map { .status($0.id, .unknown) }, toSection: .statuses)
|
snapshot.appendItems(results.statuses.map { .status($0.id, .unknown) }, toSection: .statuses)
|
||||||
self.mastodonController.persistentContainer.addAll(statuses: results.statuses)
|
self.mastodonController.cache.addAll(statuses: results.statuses)
|
||||||
self.mastodonController.persistentContainer.addAll(accounts: results.statuses.map { $0.account })
|
self.mastodonController.cache.addAll(accounts: results.statuses.map { $0.account })
|
||||||
}
|
}
|
||||||
self.dataSource.apply(snapshot)
|
self.dataSource.apply(snapshot)
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,16 +73,16 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController {
|
||||||
|
|
||||||
if accountIDs == nil {
|
if accountIDs == nil {
|
||||||
// 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.cache.status(for: statusID) else {
|
||||||
fatalError("Missing cached status \(statusID)")
|
fatalError("Missing cached status \(statusID)")
|
||||||
}
|
}
|
||||||
|
|
||||||
tableView.tableFooterView = UIActivityIndicatorView(style: .large)
|
tableView.tableFooterView = UIActivityIndicatorView(style: .large)
|
||||||
|
|
||||||
let request = actionType == .favorite ? Status.getFavourites(status.id) : Status.getReblogs(status.id)
|
let request = actionType == .favorite ? Status.getFavourites(status) : Status.getReblogs(status)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
guard case let .success(accounts, _) = response else { fatalError() }
|
guard case let .success(accounts, _) = response else { fatalError() }
|
||||||
self.mastodonController.persistentContainer.addAll(accounts: accounts)
|
self.mastodonController.cache.addAll(accounts: accounts)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.accountIDs = accounts.map { $0.id }
|
self.accountIDs = accounts.map { $0.id }
|
||||||
self.tableView.tableFooterView = nil
|
self.tableView.tableFooterView = nil
|
||||||
|
|
|
@ -30,7 +30,7 @@ extension MenuPreviewProvider {
|
||||||
|
|
||||||
func actionsForProfile(accountID: String, sourceView: UIView?) -> [UIAction] {
|
func actionsForProfile(accountID: String, sourceView: UIView?) -> [UIAction] {
|
||||||
guard let mastodonController = mastodonController,
|
guard let mastodonController = mastodonController,
|
||||||
let account = mastodonController.persistentContainer.account(for: accountID) else { return [] }
|
let account = mastodonController.cache.account(for: accountID) else { return [] }
|
||||||
return [
|
return [
|
||||||
createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { (_) in
|
createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { (_) in
|
||||||
self.navigationDelegate?.compose(mentioning: account.acct)
|
self.navigationDelegate?.compose(mentioning: account.acct)
|
||||||
|
@ -61,7 +61,7 @@ extension MenuPreviewProvider {
|
||||||
|
|
||||||
func actionsForStatus(statusID: String, sourceView: UIView?) -> [UIAction] {
|
func actionsForStatus(statusID: String, sourceView: UIView?) -> [UIAction] {
|
||||||
guard let mastodonController = mastodonController,
|
guard let mastodonController = mastodonController,
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID) else { return [] }
|
let status = mastodonController.cache.status(for: statusID) else { return [] }
|
||||||
return [
|
return [
|
||||||
createAction(identifier: "reply", title: "Reply", systemImageName: "arrowshape.turn.up.left", handler: { (_) in
|
createAction(identifier: "reply", title: "Reply", systemImageName: "arrowshape.turn.up.left", handler: { (_) in
|
||||||
self.navigationDelegate?.reply(to: statusID)
|
self.navigationDelegate?.reply(to: statusID)
|
||||||
|
|
|
@ -203,15 +203,16 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func moreOptions(forStatus statusID: String) -> UIActivityViewController {
|
private func moreOptions(forStatus statusID: String) -> UIActivityViewController {
|
||||||
guard let status = apiController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
guard let status = apiController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||||
guard let url = status.url else { fatalError("Missing url for status \(statusID)") }
|
guard let url = status.url else { fatalError("Missing url for status \(statusID)") }
|
||||||
var customActivites: [UIActivity] = [OpenInSafariActivity()]
|
var customActivites: [UIActivity] = [OpenInSafariActivity()]
|
||||||
|
|
||||||
let bookmarked = status.bookmarked ?? false
|
if let bookmarked = status.bookmarked {
|
||||||
customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0)
|
customActivites.insert(bookmarked ? UnbookmarkStatusActivity() : BookmarkStatusActivity(), at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
if status.account.id == apiController.account.id {
|
if status.account == apiController.account,
|
||||||
let pinned = status.pinned ?? false
|
let pinned = status.pinned {
|
||||||
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
|
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +222,7 @@ extension TuskerNavigationDelegate where Self: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func moreOptions(forAccount accountID: String) -> UIActivityViewController {
|
private func moreOptions(forAccount accountID: String) -> UIActivityViewController {
|
||||||
guard let account = apiController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
|
guard let account = apiController.cache.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
|
||||||
return moreOptions(forURL: account.url)
|
return moreOptions(forURL: account.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ class AccountTableViewCell: UITableViewCell {
|
||||||
@objc func updateUIForPrefrences() {
|
@objc func updateUIForPrefrences() {
|
||||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||||
|
|
||||||
guard let account = mastodonController.persistentContainer.account(for: accountID) else {
|
guard let account = mastodonController.cache.account(for: accountID) else {
|
||||||
fatalError("Missing cached account \(accountID!)")
|
fatalError("Missing cached account \(accountID!)")
|
||||||
}
|
}
|
||||||
displayNameLabel.updateForAccountDisplayName(account: account)
|
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||||
|
@ -42,7 +42,7 @@ class AccountTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
func updateUI(accountID: String) {
|
func updateUI(accountID: String) {
|
||||||
self.accountID = accountID
|
self.accountID = accountID
|
||||||
guard let account = mastodonController.persistentContainer.account(for: accountID) else {
|
guard let account = mastodonController.cache.account(for: accountID) else {
|
||||||
fatalError("Missing cached account \(accountID)")
|
fatalError("Missing cached account \(accountID)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class ComposeStatusReplyView: UIView {
|
||||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(for status: StatusMO) {
|
func updateUI(for status: Status) {
|
||||||
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
||||||
usernameLabel.text = "@\(status.account.acct)"
|
usernameLabel.text = "@\(status.account.acct)"
|
||||||
statusContentTextView.overrideMastodonController = mastodonController
|
statusContentTextView.overrideMastodonController = mastodonController
|
||||||
|
|
|
@ -16,7 +16,6 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
var mastodonController: MastodonController! { delegate?.apiController }
|
var mastodonController: MastodonController! { delegate?.apiController }
|
||||||
|
|
||||||
@IBOutlet weak var actionImageView: UIImageView!
|
@IBOutlet weak var actionImageView: UIImageView!
|
||||||
@IBOutlet weak var verticalStackView: UIStackView!
|
|
||||||
@IBOutlet weak var actionAvatarStackView: UIStackView!
|
@IBOutlet weak var actionAvatarStackView: UIStackView!
|
||||||
@IBOutlet weak var timestampLabel: UILabel!
|
@IBOutlet weak var timestampLabel: UILabel!
|
||||||
@IBOutlet weak var actionLabel: UILabel!
|
@IBOutlet weak var actionLabel: UILabel!
|
||||||
|
@ -39,8 +38,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateUIForPreferences() {
|
@objc func updateUIForPreferences() {
|
||||||
// todo: is this compactMap necessary?
|
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account }
|
||||||
let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
|
|
||||||
updateActionLabel(people: people)
|
updateActionLabel(people: people)
|
||||||
|
|
||||||
for case let imageView as UIImageView in actionAvatarStackView.arrangedSubviews {
|
for case let imageView as UIImageView in actionAvatarStackView.arrangedSubviews {
|
||||||
|
@ -54,7 +52,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
self.group = group
|
self.group = group
|
||||||
|
|
||||||
guard let firstNotification = group.notifications.first else { fatalError() }
|
guard let firstNotification = mastodonController.cache.notification(for: group.notificationIDs.first!) else { fatalError() }
|
||||||
let status = firstNotification.status!
|
let status = firstNotification.status!
|
||||||
self.statusID = status.id
|
self.statusID = status.id
|
||||||
|
|
||||||
|
@ -69,10 +67,9 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
|
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account }
|
||||||
|
|
||||||
actionAvatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
actionAvatarStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
var imageViews = [UIImageView]()
|
|
||||||
for account in people {
|
for account in people {
|
||||||
let imageView = UIImageView()
|
let imageView = UIImageView()
|
||||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -86,18 +83,11 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actionAvatarStackView.addArrangedSubview(imageView)
|
actionAvatarStackView.addArrangedSubview(imageView)
|
||||||
imageViews.append(imageView)
|
NSLayoutConstraint.activate([
|
||||||
|
imageView.widthAnchor.constraint(equalToConstant: 30),
|
||||||
// don't add more avatars if they would overflow or squeeze the timestamp label
|
imageView.heightAnchor.constraint(equalToConstant: 30)
|
||||||
let avatarViewsWidth = 30 * CGFloat(imageViews.count)
|
])
|
||||||
let avatarMarginsWidth = 4 * CGFloat(max(0, imageViews.count - 1))
|
|
||||||
let maxAvatarStackWidth = verticalStackView.bounds.width - timestampLabel.bounds.width - 8
|
|
||||||
let remainingWidth = maxAvatarStackWidth - avatarViewsWidth - avatarMarginsWidth
|
|
||||||
if remainingWidth < 34 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
NSLayoutConstraint.activate(imageViews.map { $0.widthAnchor.constraint(equalTo: $0.heightAnchor) })
|
|
||||||
|
|
||||||
updateTimestamp()
|
updateTimestamp()
|
||||||
|
|
||||||
|
@ -108,7 +98,8 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTimestamp() {
|
func updateTimestamp() {
|
||||||
guard let notification = group.notifications.first else {
|
guard let id = group.notificationIDs.first,
|
||||||
|
let notification = mastodonController.cache.notification(for: id) else {
|
||||||
fatalError("Missing cached notification")
|
fatalError("Missing cached notification")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +126,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateActionLabel(people: [AccountMO]) {
|
func updateActionLabel(people: [Account]) {
|
||||||
let verb: String
|
let verb: String
|
||||||
switch group.kind {
|
switch group.kind {
|
||||||
case .favourite:
|
case .favourite:
|
||||||
|
@ -172,7 +163,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
extension ActionNotificationGroupTableViewCell: SelectableTableViewCell {
|
extension ActionNotificationGroupTableViewCell: SelectableTableViewCell {
|
||||||
func didSelectCell() {
|
func didSelectCell() {
|
||||||
guard let delegate = delegate else { return }
|
guard let delegate = delegate else { return }
|
||||||
let notifications = group.notifications
|
let notifications = group.notificationIDs.compactMap(mastodonController.cache.notification(for:))
|
||||||
let accountIDs = notifications.map { $0.account.id }
|
let accountIDs = notifications.map { $0.account.id }
|
||||||
let action: StatusActionAccountListTableViewController.ActionType
|
let action: StatusActionAccountListTableViewController.ActionType
|
||||||
switch notifications.first!.kind {
|
switch notifications.first!.kind {
|
||||||
|
@ -193,7 +184,7 @@ extension ActionNotificationGroupTableViewCell: MenuPreviewProvider {
|
||||||
|
|
||||||
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.notificationIDs.compactMap(self.mastodonController.cache.notification(for:))
|
||||||
let accountIDs = notifications.map { $0.account.id }
|
let accountIDs = notifications.map { $0.account.id }
|
||||||
let action: StatusActionAccountListTableViewController.ActionType
|
let action: StatusActionAccountListTableViewController.ActionType
|
||||||
switch notifications.first!.kind {
|
switch notifications.first!.kind {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -19,19 +19,19 @@
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="hld-yu-Rmi">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="hld-yu-Rmi">
|
||||||
<rect key="frame" x="74" y="11" width="230" height="153"/>
|
<rect key="frame" x="74" y="11" width="230" height="153"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="hTQ-P4-gOO">
|
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hTQ-P4-gOO">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="230" height="30"/>
|
<rect key="frame" x="0.0" y="0.0" width="230" height="30"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" ambiguous="YES" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="b7l-YW-nQY">
|
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="b7l-YW-nQY">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="189.5" height="30"/>
|
<rect key="frame" x="0.0" y="0.0" width="205.5" height="30"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="30" id="9uh-oo-JSM"/>
|
<constraint firstAttribute="height" constant="30" id="9uh-oo-JSM"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
<view contentMode="scaleToFill" horizontalHuggingPriority="249" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5Ef-5g-b23">
|
<view contentMode="scaleToFill" horizontalHuggingPriority="249" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5Ef-5g-b23">
|
||||||
<rect key="frame" x="197.5" y="0.0" width="0.0" height="30"/>
|
<rect key="frame" x="205.5" y="0.0" width="0.0" height="30"/>
|
||||||
</view>
|
</view>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="752" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JN0-Bf-3qx">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="2m" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JN0-Bf-3qx">
|
||||||
<rect key="frame" x="205.5" y="0.0" width="24.5" height="30"/>
|
<rect key="frame" x="205.5" y="0.0" width="24.5" height="30"/>
|
||||||
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" weight="light" pointSize="17"/>
|
||||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
@ -78,7 +78,6 @@
|
||||||
<outlet property="actionLabel" destination="fkn-Gk-ngr" id="bBG-a8-m5G"/>
|
<outlet property="actionLabel" destination="fkn-Gk-ngr" id="bBG-a8-m5G"/>
|
||||||
<outlet property="statusContentLabel" destination="lc7-zZ-HrZ" id="jgT-LU-rXt"/>
|
<outlet property="statusContentLabel" destination="lc7-zZ-HrZ" id="jgT-LU-rXt"/>
|
||||||
<outlet property="timestampLabel" destination="JN0-Bf-3qx" id="Jlo-f6-DAi"/>
|
<outlet property="timestampLabel" destination="JN0-Bf-3qx" id="Jlo-f6-DAi"/>
|
||||||
<outlet property="verticalStackView" destination="hld-yu-Rmi" id="jvu-1u-Ok3"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
<point key="canvasLocation" x="-394.20289855072468" y="56.584821428571423"/>
|
<point key="canvasLocation" x="-394.20289855072468" y="56.584821428571423"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
|
|
|
@ -34,7 +34,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateUIForPreferences() {
|
@objc func updateUIForPreferences() {
|
||||||
let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
|
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account }
|
||||||
updateActionLabel(people: people)
|
updateActionLabel(people: people)
|
||||||
|
|
||||||
for case let imageView as UIImageView in avatarStackView.arrangedSubviews {
|
for case let imageView as UIImageView in avatarStackView.arrangedSubviews {
|
||||||
|
@ -45,7 +45,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
func updateUI(group: NotificationGroup) {
|
func updateUI(group: NotificationGroup) {
|
||||||
self.group = group
|
self.group = group
|
||||||
|
|
||||||
let people = group.notifications.compactMap { mastodonController.persistentContainer.account(for: $0.account.id) }
|
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account }
|
||||||
|
|
||||||
updateActionLabel(people: people)
|
updateActionLabel(people: people)
|
||||||
updateTimestamp()
|
updateTimestamp()
|
||||||
|
@ -71,8 +71,8 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateActionLabel(people: [AccountMO]) {
|
func updateActionLabel(people: [Account]) {
|
||||||
// todo: custom emoji in people display names
|
// todo: update to use managed objects
|
||||||
// todo: figure out how to localize this
|
// todo: figure out how to localize this
|
||||||
let peopleStr: String
|
let peopleStr: String
|
||||||
switch people.count {
|
switch people.count {
|
||||||
|
@ -88,7 +88,8 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTimestamp() {
|
func updateTimestamp() {
|
||||||
guard let notification = group.notifications.first else {
|
guard let id = group.notificationIDs.first,
|
||||||
|
let notification = mastodonController.cache.notification(for: id) else {
|
||||||
fatalError("Missing cached notification")
|
fatalError("Missing cached notification")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,14 +128,14 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
extension FollowNotificationGroupTableViewCell: SelectableTableViewCell {
|
extension FollowNotificationGroupTableViewCell: SelectableTableViewCell {
|
||||||
func didSelectCell() {
|
func didSelectCell() {
|
||||||
let accountIDs = group.notifications.map { $0.account.id }
|
let people = group.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account.id }
|
||||||
switch accountIDs.count {
|
switch people.count {
|
||||||
case 0:
|
case 0:
|
||||||
return
|
return
|
||||||
case 1:
|
case 1:
|
||||||
delegate?.selected(account: accountIDs.first!)
|
delegate?.selected(account: people.first!)
|
||||||
default:
|
default:
|
||||||
delegate?.showFollowedByList(accountIDs: accountIDs)
|
delegate?.showFollowedByList(accountIDs: people)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +145,7 @@ extension FollowNotificationGroupTableViewCell: MenuPreviewProvider {
|
||||||
|
|
||||||
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.notificationIDs.compactMap(mastodonController.cache.notification(for:)).map { $0.account.id }
|
||||||
return (content: {
|
return (content: {
|
||||||
if accountIDs.count == 1 {
|
if accountIDs.count == 1 {
|
||||||
return ProfileTableViewController(accountID: accountIDs.first!, mastodonController: mastodonController)
|
return ProfileTableViewController(accountID: accountIDs.first!, mastodonController: mastodonController)
|
||||||
|
|
|
@ -109,7 +109,8 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
@IBAction func rejectButtonPressed() {
|
@IBAction func rejectButtonPressed() {
|
||||||
let request = Account.rejectFollowRequest(account)
|
let request = Account.rejectFollowRequest(account)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
guard case .success(_, _) = response else { fatalError() }
|
guard case let .success(relationship, _) = response else { fatalError() }
|
||||||
|
self.mastodonController.cache.add(relationship: relationship)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||||
self.actionButtonsStackView.isHidden = true
|
self.actionButtonsStackView.isHidden = true
|
||||||
|
@ -125,7 +126,8 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
@IBAction func acceptButtonPressed() {
|
@IBAction func acceptButtonPressed() {
|
||||||
let request = Account.authorizeFollowRequest(account)
|
let request = Account.authorizeFollowRequest(account)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
guard case .success(_, _) = response else { fatalError() }
|
guard case let .success(relationship, _) = response else { fatalError() }
|
||||||
|
self.mastodonController.cache.add(relationship: relationship)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
||||||
self.actionButtonsStackView.isHidden = true
|
self.actionButtonsStackView.isHidden = true
|
||||||
|
|
|
@ -88,13 +88,14 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
||||||
noteTextView.setTextFromHtml(account.note)
|
noteTextView.setTextFromHtml(account.note)
|
||||||
noteTextView.setEmojis(account.emojis)
|
noteTextView.setEmojis(account.emojis)
|
||||||
|
|
||||||
// don't show relationship label for the user's own account
|
|
||||||
if accountID != mastodonController.account.id {
|
if accountID != mastodonController.account.id {
|
||||||
let request = Client.getRelationships(accounts: [accountID])
|
// don't show relationship label for the user's own account
|
||||||
mastodonController.run(request) { (response) in
|
if let relationship = mastodonController.cache.relationship(for: accountID) {
|
||||||
if case let .success(results, _) = response, let relationship = results.first {
|
followsYouLabel.isHidden = !relationship.followedBy
|
||||||
|
} else {
|
||||||
|
mastodonController.cache.relationship(for: accountID) { relationship in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.followsYouLabel.isHidden = !relationship.followedBy
|
self.followsYouLabel.isHidden = !(relationship?.followedBy ?? false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,12 +123,12 @@ class ProfileHeaderTableViewCell: UITableViewCell {
|
||||||
fieldValuesStack.addArrangedSubview(valueTextView)
|
fieldValuesStack.addArrangedSubview(valueTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
if accountUpdater == nil {
|
// if accountUpdater == nil {
|
||||||
accountUpdater = mastodonController.persistentContainer.accountSubject
|
// accountUpdater = mastodonController.cache.accountSubject
|
||||||
.filter { [unowned self] in $0 == self.accountID }
|
// .filter { [unowned self] in $0.id == self.accountID }
|
||||||
.receive(on: DispatchQueue.main)
|
// .receive(on: DispatchQueue.main)
|
||||||
.sink { [unowned self] in self.updateUI(for: $0) }
|
// .sink { [unowned self] in self.updateUI(for: $0.id) }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateUIForPreferences() {
|
@objc func updateUIForPreferences() {
|
||||||
|
|
|
@ -69,6 +69,11 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
private var statusUpdater: Cancellable?
|
private var statusUpdater: Cancellable?
|
||||||
private var accountUpdater: Cancellable?
|
private var accountUpdater: Cancellable?
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
statusUpdater?.cancel()
|
||||||
|
accountUpdater?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
@ -90,27 +95,20 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
open func createObserversIfNecessary() {
|
open func createObserversIfNecessary() {
|
||||||
if statusUpdater == nil {
|
// todo: KVO on StatusMO for this?
|
||||||
statusUpdater = mastodonController.persistentContainer.statusSubject
|
// if statusUpdater == nil {
|
||||||
.filter { [unowned self] in $0 == self.statusID }
|
// statusUpdater = mastodonController.cache.statusSubject
|
||||||
.receive(on: DispatchQueue.main)
|
// .filter { [unowned self] in $0.id == self.statusID }
|
||||||
.sink { [unowned self] in
|
// .receive(on: DispatchQueue.main)
|
||||||
if let status = self.mastodonController.persistentContainer.status(for: $0) {
|
// .sink { [unowned self] in self.updateStatusState(status: $0) }
|
||||||
self.updateStatusState(status: status)
|
// }
|
||||||
}
|
//
|
||||||
}
|
// if accountUpdater == nil {
|
||||||
}
|
// accountUpdater = mastodonController.cache.accountSubject
|
||||||
|
// .filter { [unowned self] in $0.id == self.accountID }
|
||||||
if accountUpdater == nil {
|
// .receive(on: DispatchQueue.main)
|
||||||
accountUpdater = mastodonController.persistentContainer.accountSubject
|
// .sink { [unowned self] in self.updateUI(account: $0) }
|
||||||
.filter { [unowned self] in $0 == self.accountID }
|
// }
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink { [unowned self] in
|
|
||||||
if let account = self.mastodonController.persistentContainer.account(for: $0) {
|
|
||||||
self.updateUI(account: account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(statusID: String, state: StatusState) {
|
func updateUI(statusID: String, state: StatusState) {
|
||||||
|
@ -251,18 +249,18 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func favoritePressed() {
|
@IBAction func favoritePressed() {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
|
|
||||||
let oldValue = favorited
|
let oldValue = favorited
|
||||||
favorited = !favorited
|
favorited = !favorited
|
||||||
|
|
||||||
let realStatus = status.reblog ?? status
|
let realStatus: Status = status.reblog ?? status
|
||||||
let request = (favorited ? Status.favourite : Status.unfavourite)(realStatus.id)
|
let request = (favorited ? Status.favourite : Status.unfavourite)(realStatus)
|
||||||
mastodonController.run(request) { response in
|
mastodonController.run(request) { response in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if case let .success(newStatus, _) = response {
|
if case let .success(newStatus, _) = response {
|
||||||
self.favorited = newStatus.favourited
|
self.favorited = newStatus.favourited ?? false
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
self.mastodonController.cache.add(status: newStatus)
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
} else {
|
} else {
|
||||||
self.favorited = oldValue
|
self.favorited = oldValue
|
||||||
|
@ -276,18 +274,18 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func reblogPressed() {
|
@IBAction func reblogPressed() {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
|
|
||||||
let oldValue = reblogged
|
let oldValue = reblogged
|
||||||
reblogged = !reblogged
|
reblogged = !reblogged
|
||||||
|
|
||||||
let realStatus = status.reblog ?? status
|
let realStatus: Status = status.reblog ?? status
|
||||||
let request = (reblogged ? Status.reblog : Status.unreblog)(realStatus.id)
|
let request = (reblogged ? Status.reblog : Status.unreblog)(realStatus)
|
||||||
mastodonController.run(request) { response in
|
mastodonController.run(request) { response in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if case let .success(newStatus, _) = response {
|
if case let .success(newStatus, _) = response {
|
||||||
self.reblogged = newStatus.reblogged
|
self.reblogged = newStatus.reblogged ?? false
|
||||||
self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false)
|
self.mastodonController.cache.add(status: newStatus)
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
} else {
|
} else {
|
||||||
self.reblogged = oldValue
|
self.reblogged = oldValue
|
||||||
|
|
|
@ -43,7 +43,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError() }
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError() }
|
||||||
|
|
||||||
var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt)
|
var timestampAndClientText = ConversationMainStatusTableViewCell.dateFormatter.string(from: status.createdAt)
|
||||||
if let application = status.applicationName {
|
if let application = status.application {
|
||||||
timestampAndClientText += " • \(application)"
|
timestampAndClientText += " • \(application)"
|
||||||
}
|
}
|
||||||
timestampAndClientLabel.text = timestampAndClientText
|
timestampAndClientLabel.text = timestampAndClientText
|
||||||
|
|
|
@ -47,16 +47,13 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
override func createObserversIfNecessary() {
|
override func createObserversIfNecessary() {
|
||||||
super.createObserversIfNecessary()
|
super.createObserversIfNecessary()
|
||||||
|
|
||||||
if rebloggerAccountUpdater == nil {
|
// todo: use KVO on reblogger account?
|
||||||
rebloggerAccountUpdater = mastodonController.persistentContainer.accountSubject
|
// if rebloggerAccountUpdater == nil {
|
||||||
.filter { [unowned self] in $0 == self.rebloggerID }
|
// rebloggerAccountUpdater = mastodonController.cache.accountSubject
|
||||||
.receive(on: DispatchQueue.main)
|
// .filter { [unowned self] in $0.id == self.rebloggerID }
|
||||||
.sink { [unowned self] in
|
// .receive(on: DispatchQueue.main)
|
||||||
if let reblogger = self.mastodonController.persistentContainer.account(for: $0) {
|
// .sink { [unowned self] in self.updateRebloggerLabel(reblogger: $0) }
|
||||||
self.updateRebloggerLabel(reblogger: reblogger)
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateUI(statusID: String, state: StatusState) {
|
override func updateUI(statusID: String, state: StatusState) {
|
||||||
|
@ -80,7 +77,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
|
|
||||||
updateTimestamp()
|
updateTimestamp()
|
||||||
|
|
||||||
let pinned = status.pinned ?? false
|
let pinned = status.pinned
|
||||||
pinImageView.isHidden = !(pinned && showPinned)
|
pinImageView.isHidden = !(pinned && showPinned)
|
||||||
timestampLabel.isHidden = !pinImageView.isHidden
|
timestampLabel.isHidden = !pinImageView.isHidden
|
||||||
}
|
}
|
||||||
|
@ -136,7 +133,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
func reply() {
|
func reply() {
|
||||||
if Preferences.shared.mentionReblogger,
|
if Preferences.shared.mentionReblogger,
|
||||||
let rebloggerID = rebloggerID,
|
let rebloggerID = rebloggerID,
|
||||||
let rebloggerAccount = mastodonController.persistentContainer.account(for: rebloggerID) {
|
let rebloggerAccount = mastodonController.cache.account(for: rebloggerID) {
|
||||||
delegate?.reply(to: statusID, mentioningAcct: rebloggerAccount.acct)
|
delegate?.reply(to: statusID, mentioningAcct: rebloggerAccount.acct)
|
||||||
} else {
|
} else {
|
||||||
delegate?.reply(to: statusID)
|
delegate?.reply(to: statusID)
|
||||||
|
@ -179,19 +176,19 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
||||||
|
|
||||||
func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
|
func leadingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
|
||||||
guard let mastodonController = mastodonController else { return nil }
|
guard let mastodonController = mastodonController else { return nil }
|
||||||
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
|
|
||||||
let favoriteTitle: String
|
let favoriteTitle: String
|
||||||
let favoriteRequest: Request<Status>
|
let favoriteRequest: Request<Status>
|
||||||
let favoriteColor: UIColor
|
let favoriteColor: UIColor
|
||||||
if status.favourited {
|
if status.favourited ?? false {
|
||||||
favoriteTitle = "Unfavorite"
|
favoriteTitle = "Unfavorite"
|
||||||
favoriteRequest = Status.unfavourite(status.id)
|
favoriteRequest = Status.unfavourite(status)
|
||||||
favoriteColor = UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1)
|
favoriteColor = UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
favoriteTitle = "Favorite"
|
favoriteTitle = "Favorite"
|
||||||
favoriteRequest = Status.favourite(status.id)
|
favoriteRequest = Status.favourite(status)
|
||||||
favoriteColor = UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1)
|
favoriteColor = UIColor(displayP3Red: 1, green: 204/255, blue: 0, alpha: 1)
|
||||||
}
|
}
|
||||||
let favorite = UIContextualAction(style: .normal, title: favoriteTitle) { (action, view, completion) in
|
let favorite = UIContextualAction(style: .normal, title: favoriteTitle) { (action, view, completion) in
|
||||||
|
@ -202,7 +199,7 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
completion(true)
|
completion(true)
|
||||||
mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
mastodonController.cache.add(status: status)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -212,13 +209,13 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
||||||
let reblogTitle: String
|
let reblogTitle: String
|
||||||
let reblogRequest: Request<Status>
|
let reblogRequest: Request<Status>
|
||||||
let reblogColor: UIColor
|
let reblogColor: UIColor
|
||||||
if status.reblogged {
|
if status.reblogged ?? false {
|
||||||
reblogTitle = "Unreblog"
|
reblogTitle = "Unreblog"
|
||||||
reblogRequest = Status.unreblog(status.id)
|
reblogRequest = Status.unreblog(status)
|
||||||
reblogColor = UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1)
|
reblogColor = UIColor(displayP3Red: 235/255, green: 77/255, blue: 62/255, alpha: 1)
|
||||||
} else {
|
} else {
|
||||||
reblogTitle = "Reblog"
|
reblogTitle = "Reblog"
|
||||||
reblogRequest = Status.reblog(status.id)
|
reblogRequest = Status.reblog(status)
|
||||||
reblogColor = tintColor
|
reblogColor = tintColor
|
||||||
}
|
}
|
||||||
let reblog = UIContextualAction(style: .normal, title: reblogTitle) { (action, view, completion) in
|
let reblog = UIContextualAction(style: .normal, title: reblogTitle) { (action, view, completion) in
|
||||||
|
@ -229,7 +226,7 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
completion(true)
|
completion(true)
|
||||||
mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false)
|
mastodonController.cache.add(status: status)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class StatusContentTextView: ContentTextView {
|
||||||
let mention: Mention?
|
let mention: Mention?
|
||||||
if let statusID = statusID,
|
if let statusID = statusID,
|
||||||
let mastodonController = mastodonController,
|
let mastodonController = mastodonController,
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID) {
|
let status = mastodonController.cache.status(for: statusID) {
|
||||||
mention = status.mentions.first { (mention) in
|
mention = status.mentions.first { (mention) in
|
||||||
// Mastodon and Pleroma include the @ in the <a> text, GNU Social does not
|
// Mastodon and Pleroma include the @ in the <a> text, GNU Social does not
|
||||||
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host
|
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host
|
||||||
|
@ -42,7 +42,7 @@ class StatusContentTextView: ContentTextView {
|
||||||
let hashtag: Hashtag?
|
let hashtag: Hashtag?
|
||||||
if let statusID = statusID,
|
if let statusID = statusID,
|
||||||
let mastodonController = mastodonController,
|
let mastodonController = mastodonController,
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID) {
|
let status = mastodonController.cache.status(for: statusID) {
|
||||||
hashtag = status.hashtags.first { (hashtag) in
|
hashtag = status.hashtags.first { (hashtag) in
|
||||||
hashtag.url == url
|
hashtag.url == url
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,62 +38,62 @@ struct XCBActions {
|
||||||
|
|
||||||
private static func getStatus(from request: XCBRequest, session: XCBSession, completion: @escaping (Status) -> Void) {
|
private static func getStatus(from request: XCBRequest, session: XCBSession, completion: @escaping (Status) -> Void) {
|
||||||
if let id = request.arguments["statusID"] {
|
if let id = request.arguments["statusID"] {
|
||||||
let request = Client.getStatus(id: id)
|
mastodonController.cache.status(for: id) { (status) in
|
||||||
mastodonController.run(request) { (response) in
|
if let status = status {
|
||||||
guard case let .success(status, _) = response else {
|
completion(status)
|
||||||
|
} else {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": "Could not get status with ID \(id)"
|
"error": "Could not get status with ID \(id)"
|
||||||
])
|
])
|
||||||
return
|
|
||||||
}
|
}
|
||||||
completion(status)
|
|
||||||
}
|
}
|
||||||
} else if let searchQuery = request.arguments["statusURL"] {
|
} else if let searchQuery = request.arguments["statusURL"] {
|
||||||
let request = Client.search(query: searchQuery)
|
let request = Client.search(query: searchQuery)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(results, _) = response,
|
if case let .success(results, _) = response,
|
||||||
let status = results.statuses.first {
|
let status = results.statuses.first {
|
||||||
|
mastodonController.cache.add(status: status)
|
||||||
completion(status)
|
completion(status)
|
||||||
} else {
|
} else {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": "Could not find status by searching '\(searchQuery)'"
|
"error": "Could not find status by searching '\(searchQuery)'"
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": "No status provided. Specify either instance-local statusID or remote statusURL."
|
"error": "No status provided. Specify either instance-local statusID or remote statusURL."
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getAccount(from request: XCBRequest, session: XCBSession, completion: @escaping (Account) -> Void) {
|
private static func getAccount(from request: XCBRequest, session: XCBSession, completion: @escaping (Account) -> Void) {
|
||||||
if let id = request.arguments["accountID"] {
|
if let id = request.arguments["accountID"] {
|
||||||
let request = Client.getAccount(id: id)
|
mastodonController.cache.account(for: id) { (account) in
|
||||||
mastodonController.run(request) { (response) in
|
if let account = account {
|
||||||
guard case let .success(account, _) = response else {
|
completion(account)
|
||||||
|
} else {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": "Could not get account with ID \(id)"
|
"error": "Could not get account with ID \(id)"
|
||||||
])
|
])
|
||||||
return
|
|
||||||
}
|
}
|
||||||
completion(account)
|
|
||||||
}
|
}
|
||||||
} else if let searchQuery = request.arguments["accountURL"] {
|
} else if let searchQuery = request.arguments["accountURL"] {
|
||||||
let request = Client.search(query: searchQuery)
|
let request = Client.search(query: searchQuery)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(results, _) = response {
|
if case let .success(results, _) = response {
|
||||||
if let account = results.accounts.first {
|
if let account = results.accounts.first {
|
||||||
|
mastodonController.cache.add(account: account)
|
||||||
completion(account)
|
completion(account)
|
||||||
} else {
|
} else {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": "Could not find account by searching '\(searchQuery)'"
|
"error": "Could not find account by searching '\(searchQuery)'"
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
} else if case let .failure(error) = response {
|
} else if case let .failure(error) = response {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": error.localizedDescription
|
"error": error.localizedDescription
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let acct = request.arguments["acct"] {
|
} else if let acct = request.arguments["acct"] {
|
||||||
|
@ -101,22 +101,23 @@ struct XCBActions {
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case let .success(accounts, _) = response {
|
if case let .success(accounts, _) = response {
|
||||||
if let account = accounts.first {
|
if let account = accounts.first {
|
||||||
|
mastodonController.cache.add(account: account)
|
||||||
completion(account)
|
completion(account)
|
||||||
} else {
|
} else {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": "Could not find account \(acct)"
|
"error": "Could not find account \(acct)"
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
} else if case let .failure(error) = response {
|
} else if case let .failure(error) = response {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": error.localizedDescription
|
"error": error.localizedDescription
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": "No status provided. Specify either instance-local ID, account URL, or qualified username."
|
"error": "No status provided. Specify either instance-local ID, account URL, or qualified username."
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ struct XCBActions {
|
||||||
guard CharacterCounter.count(text: status) <= mastodonController.instance.maxStatusCharacters ?? 500 else {
|
guard CharacterCounter.count(text: status) <= mastodonController.instance.maxStatusCharacters ?? 500 else {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": "Too many characters. Instance maximum is \(mastodonController.instance.maxStatusCharacters ?? 500)"
|
"error": "Too many characters. Instance maximum is \(mastodonController.instance.maxStatusCharacters ?? 500)"
|
||||||
])
|
])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let request = Client.createStatus(text: status, visibility: Preferences.shared.defaultPostVisibility)
|
let request = Client.createStatus(text: status, visibility: Preferences.shared.defaultPostVisibility)
|
||||||
|
@ -150,11 +151,11 @@ struct XCBActions {
|
||||||
session.complete(with: .success, additionalData: [
|
session.complete(with: .success, additionalData: [
|
||||||
"statusURL": status.url?.absoluteString,
|
"statusURL": status.url?.absoluteString,
|
||||||
"statusURI": status.uri
|
"statusURI": status.uri
|
||||||
])
|
])
|
||||||
} else if case let .failure(error) = response {
|
} else if case let .failure(error) = response {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": error.localizedDescription
|
"error": error.localizedDescription
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -178,7 +179,7 @@ struct XCBActions {
|
||||||
} catch {
|
} catch {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": error.localizedDescription
|
"error": error.localizedDescription
|
||||||
])
|
])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +192,7 @@ struct XCBActions {
|
||||||
"posted": status.createdAt.timeIntervalSince1970.description,
|
"posted": status.createdAt.timeIntervalSince1970.description,
|
||||||
"content": content,
|
"content": content,
|
||||||
"reblog": status.reblog?.id
|
"reblog": status.reblog?.id
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,19 +204,20 @@ struct XCBActions {
|
||||||
statusAction(request: Status.reblog, alertTitle: "Reblog status?", request, session, silent)
|
statusAction(request: Status.reblog, alertTitle: "Reblog status?", request, session, silent)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func statusAction(request: @escaping (String) -> Request<Status>, alertTitle: String, _ url: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
static func statusAction(request: @escaping (Status) -> Request<Status>, alertTitle: String, _ url: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
||||||
func performAction(status: Status, completion: ((Status) -> Void)?) {
|
func performAction(status: Status, completion: ((Status) -> Void)?) {
|
||||||
mastodonController.run(request(status.id)) { (response) in
|
mastodonController.run(request(status)) { (response) in
|
||||||
if case let .success(status, _) = response {
|
if case let .success(status, _) = response {
|
||||||
|
mastodonController.cache.add(status: status)
|
||||||
completion?(status)
|
completion?(status)
|
||||||
session.complete(with: .success, additionalData: [
|
session.complete(with: .success, additionalData: [
|
||||||
"statusURL": status.url?.absoluteString,
|
"statusURL": status.url?.absoluteString,
|
||||||
"statusURI": status.uri
|
"statusURI": status.uri
|
||||||
])
|
])
|
||||||
} else if case let .failure(error) = response {
|
} else if case let .failure(error) = response {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": error.localizedDescription
|
"error": error.localizedDescription
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,7 +271,7 @@ struct XCBActions {
|
||||||
"url": account.url.absoluteString,
|
"url": account.url.absoluteString,
|
||||||
"avatarURL": account.avatar.absoluteString,
|
"avatarURL": account.avatar.absoluteString,
|
||||||
"headerURL": account.header.absoluteString
|
"headerURL": account.header.absoluteString
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,21 +286,22 @@ struct XCBActions {
|
||||||
"url": account.url.absoluteString,
|
"url": account.url.absoluteString,
|
||||||
"avatarURL": account.avatar.absoluteString,
|
"avatarURL": account.avatar.absoluteString,
|
||||||
"headerURL": account.header.absoluteString
|
"headerURL": account.header.absoluteString
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
static func followUser(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
static func followUser(_ request: XCBRequest, _ session: XCBSession, _ silent: Bool?) {
|
||||||
func performAction(_ account: Account) {
|
func performAction(_ account: Account) {
|
||||||
let request = Account.follow(account.id)
|
let request = Account.follow(account.id)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
if case .success(_, _) = response {
|
if case let .success(relationship, _) = response {
|
||||||
|
mastodonController.cache.add(relationship: relationship)
|
||||||
session.complete(with: .success, additionalData: [
|
session.complete(with: .success, additionalData: [
|
||||||
"url": account.url.absoluteString
|
"url": account.url.absoluteString
|
||||||
])
|
])
|
||||||
} else if case let .failure(error) = response {
|
} else if case let .failure(error) = response {
|
||||||
session.complete(with: .error, additionalData: [
|
session.complete(with: .error, additionalData: [
|
||||||
"error": error.localizedDescription
|
"error": error.localizedDescription
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue