forked from shadowfacts/Tusker
Start converting UI to use CoreData backed objects instead of API
objects directly
This commit is contained in:
parent
a0e95d4577
commit
2c8ba878b7
|
@ -58,7 +58,7 @@ extension AccountMO {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.acct = account.acct
|
self.acct = account.acct
|
||||||
self.avatar = account.avatar
|
self.avatar = account.avatarStatic // we don't animate avatars
|
||||||
self.bot = account.bot ?? false
|
self.bot = account.bot ?? false
|
||||||
self.createdAt = account.createdAt
|
self.createdAt = account.createdAt
|
||||||
self.displayName = account.displayName
|
self.displayName = account.displayName
|
||||||
|
@ -66,7 +66,7 @@ extension AccountMO {
|
||||||
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.header
|
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.moved = account.moved ?? false
|
self.moved = account.moved ?? false
|
||||||
|
|
|
@ -37,19 +37,33 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func upsert(status: Status) {
|
||||||
|
if let statusMO = self.status(for: status.id, in: self.backgroundContext) {
|
||||||
|
statusMO.updateFrom(apiStatus: status, container: self)
|
||||||
|
} else {
|
||||||
|
_ = StatusMO(apiStatus: status, container: self, context: self.backgroundContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addOrUpdate(status: Status, save: Bool = true) {
|
func addOrUpdate(status: Status, save: Bool = true) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
if let statusMO = self.status(for: status.id, in: self.backgroundContext) {
|
self.upsert(status: status)
|
||||||
statusMO.updateFrom(apiStatus: status, container: self)
|
|
||||||
} else {
|
|
||||||
_ = StatusMO(apiStatus: status, container: self, context: self.backgroundContext)
|
|
||||||
}
|
|
||||||
if save, self.backgroundContext.hasChanges {
|
if save, self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
|
||||||
|
backgroundContext.perform {
|
||||||
|
statuses.forEach(self.upsert(status:))
|
||||||
|
if self.backgroundContext.hasChanges {
|
||||||
|
try! self.backgroundContext.save()
|
||||||
|
}
|
||||||
|
completion?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func account(for id: String, in context: NSManagedObjectContext? = nil) -> AccountMO? {
|
func account(for id: String, in context: NSManagedObjectContext? = nil) -> AccountMO? {
|
||||||
let context = context ?? viewContext
|
let context = context ?? viewContext
|
||||||
let request: NSFetchRequest<AccountMO> = AccountMO.fetchRequest()
|
let request: NSFetchRequest<AccountMO> = AccountMO.fetchRequest()
|
||||||
|
@ -62,17 +76,31 @@ class MastodonCachePersistentStore: NSPersistentContainer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func upsert(account: Account) {
|
||||||
|
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
|
||||||
|
accountMO.updateFrom(apiAccount: account, container: self)
|
||||||
|
} else {
|
||||||
|
_ = AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addOrUpdate(account: Account, save: Bool = true) {
|
func addOrUpdate(account: Account, save: Bool = true) {
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
if let accountMO = self.account(for: account.id, in: self.backgroundContext) {
|
self.upsert(account: account)
|
||||||
accountMO.updateFrom(apiAccount: account, container: self)
|
|
||||||
} else {
|
|
||||||
_ = AccountMO(apiAccount: account, container: self, context: self.backgroundContext)
|
|
||||||
}
|
|
||||||
if save, self.backgroundContext.hasChanges {
|
if save, self.backgroundContext.hasChanges {
|
||||||
try! self.backgroundContext.save()
|
try! self.backgroundContext.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addAll(accounts: [Account], completion: (() -> Void)? = nil) {
|
||||||
|
backgroundContext.perform {
|
||||||
|
accounts.forEach(self.upsert(account:))
|
||||||
|
if self.backgroundContext.hasChanges {
|
||||||
|
try! self.backgroundContext.save()
|
||||||
|
}
|
||||||
|
completion?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ public final class StatusMO: NSManagedObject {
|
||||||
@NSManaged public var reblogged: Bool
|
@NSManaged public var reblogged: Bool
|
||||||
@NSManaged public var reblogsCount: Int
|
@NSManaged public var reblogsCount: Int
|
||||||
@NSManaged public var sensitive: Bool
|
@NSManaged public var sensitive: Bool
|
||||||
|
@NSManaged public var spoilerText: String
|
||||||
@NSManaged public var uri: String // todo: are both uri and url necessary?
|
@NSManaged public var uri: String // todo: are both uri and url necessary?
|
||||||
@NSManaged public var url: URL?
|
@NSManaged public var url: URL?
|
||||||
@NSManaged private var visibilityString: String
|
@NSManaged private var visibilityString: String
|
||||||
|
@ -95,6 +96,7 @@ extension StatusMO {
|
||||||
self.reblogged = status.reblogged ?? false
|
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.uri = status.uri
|
self.uri = status.uri
|
||||||
self.visibility = status.visibility
|
self.visibility = status.visibility
|
||||||
self.account = container.account(for: status.account.id, in: context) ?? AccountMO(apiAccount: status.account, container: container, context: context)
|
self.account = container.account(for: status.account.id, in: context) ?? AccountMO(apiAccount: status.account, container: container, context: context)
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
<attribute name="reblogged" optional="YES" 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="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="spoilerText" attributeType="String"/>
|
||||||
<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"/>
|
||||||
|
@ -57,6 +58,6 @@
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="313"/>
|
<element name="Account" positionX="169.21875" positionY="78.9609375" width="128" height="313"/>
|
||||||
<element name="Status" positionX="-63" positionY="-18" width="128" height="388"/>
|
<element name="Status" positionX="-63" positionY="-18" width="128" height="403"/>
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
|
@ -9,7 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
extension Account {
|
extension AccountMO {
|
||||||
|
|
||||||
var displayOrUserName: String {
|
var displayOrUserName: String {
|
||||||
if displayName.isEmpty {
|
if displayName.isEmpty {
|
||||||
|
@ -31,7 +31,7 @@ extension Account {
|
||||||
|
|
||||||
private func stripCustomEmoji(from string: String) -> String {
|
private func stripCustomEmoji(from string: String) -> String {
|
||||||
let range = NSRange(location: 0, length: string.utf16.count)
|
let range = NSRange(location: 0, length: string.utf16.count)
|
||||||
return Account.customEmojiRegex.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "")
|
return AccountMO.customEmojiRegex.stringByReplacingMatches(in: string, options: [], range: range, withTemplate: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Foundation
|
||||||
private let decoder = PropertyListDecoder()
|
private let decoder = PropertyListDecoder()
|
||||||
private let encoder = PropertyListEncoder()
|
private let encoder = PropertyListEncoder()
|
||||||
|
|
||||||
|
// todo: invalidate cache on underlying data change using KVO?
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
struct LazilyDecoding<Enclosing, Value: Codable> {
|
struct LazilyDecoding<Enclosing, Value: Codable> {
|
||||||
|
|
||||||
|
|
|
@ -64,12 +64,7 @@ class MastodonCache {
|
||||||
|
|
||||||
func addAll(statuses: [Status]) {
|
func addAll(statuses: [Status]) {
|
||||||
statuses.forEach(add)
|
statuses.forEach(add)
|
||||||
if let container = mastodonController?.persistentContainer {
|
mastodonController?.persistentContainer.addAll(statuses: statuses)
|
||||||
statuses.forEach { container.addOrUpdate(status: $0, save: false) }
|
|
||||||
container.backgroundContext.perform {
|
|
||||||
try! container.backgroundContext.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Accounts
|
// MARK: - Accounts
|
||||||
|
@ -104,12 +99,7 @@ class MastodonCache {
|
||||||
|
|
||||||
func addAll(accounts: [Account]) {
|
func addAll(accounts: [Account]) {
|
||||||
accounts.forEach(add)
|
accounts.forEach(add)
|
||||||
if let container = mastodonController?.persistentContainer {
|
mastodonController?.persistentContainer.addAll(accounts: accounts)
|
||||||
accounts.forEach { container.addOrUpdate(account: $0, save: false) }
|
|
||||||
container.backgroundContext.perform {
|
|
||||||
try! container.backgroundContext.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Relationships
|
// MARK: - Relationships
|
||||||
|
|
|
@ -213,7 +213,8 @@ class ComposeViewController: UIViewController {
|
||||||
replyAvatarImageViewTopConstraint!.isActive = true
|
replyAvatarImageViewTopConstraint!.isActive = true
|
||||||
|
|
||||||
inReplyToContainer.isHidden = false
|
inReplyToContainer.isHidden = false
|
||||||
inReplyToLabel.text = "In reply to \(inReplyTo.account.displayOrUserName)"
|
// todo: update to use managed objects
|
||||||
|
inReplyToLabel.text = "In reply to \(inReplyTo.account.displayName)"
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
|
|
@ -128,7 +128,7 @@ class ProfileTableViewController: EnhancedTableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateUIForPreferences() {
|
@objc func updateUIForPreferences() {
|
||||||
guard let accountID = accountID, let account = mastodonController.cache.account(for: accountID) else { return }
|
guard let accountID = accountID, let account = mastodonController.persistentContainer.account(for: accountID) else { return }
|
||||||
navigationItem.title = account.displayNameWithoutCustomEmoji
|
navigationItem.title = account.displayNameWithoutCustomEmoji
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,12 +59,15 @@ class TimelineTableViewController: EnhancedTableViewController {
|
||||||
let request = Client.getStatuses(timeline: timeline)
|
let request = Client.getStatuses(timeline: timeline)
|
||||||
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.cache.addAll(statuses: statuses)
|
// self.mastodonController.cache.addAll(statuses: statuses)
|
||||||
|
// todo: possible race condition here? we update the underlying data before waiting to reload the table view
|
||||||
self.timelineSegments.insert(statuses.map { ($0.id, .unknown) }, at: 0)
|
self.timelineSegments.insert(statuses.map { ($0.id, .unknown) }, at: 0)
|
||||||
self.newer = pagination?.newer
|
self.newer = pagination?.newer
|
||||||
self.older = pagination?.older
|
self.older = pagination?.older
|
||||||
DispatchQueue.main.async {
|
self.mastodonController.persistentContainer.addAll(statuses: statuses) {
|
||||||
self.tableView.reloadData()
|
DispatchQueue.main.async {
|
||||||
|
self.tableView.reloadData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,13 +104,15 @@ class TimelineTableViewController: 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.cache.addAll(statuses: newStatuses)
|
// self.mastodonController.cache.addAll(statuses: newStatuses)
|
||||||
let newRows = self.timelineSegments.last!.count..<(self.timelineSegments.last!.count + newStatuses.count)
|
let newRows = self.timelineSegments.last!.count..<(self.timelineSegments.last!.count + newStatuses.count)
|
||||||
let newIndexPaths = newRows.map { IndexPath(row: $0, section: self.timelineSegments.count - 1) }
|
let newIndexPaths = newRows.map { IndexPath(row: $0, section: self.timelineSegments.count - 1) }
|
||||||
self.timelineSegments[self.timelineSegments.count - 1].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
|
self.timelineSegments[self.timelineSegments.count - 1].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
|
||||||
DispatchQueue.main.async {
|
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
|
||||||
UIView.performWithoutAnimation {
|
DispatchQueue.main.async {
|
||||||
self.tableView.insertRows(at: newIndexPaths, with: .none)
|
UIView.performWithoutAnimation {
|
||||||
|
self.tableView.insertRows(at: newIndexPaths, with: .none)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,25 +138,27 @@ class TimelineTableViewController: 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.newer = pagination?.newer
|
self.newer = pagination?.newer
|
||||||
self.mastodonController.cache.addAll(statuses: newStatuses)
|
// self.mastodonController.cache.addAll(statuses: newStatuses)
|
||||||
self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0)
|
self.timelineSegments[0].insert(contentsOf: newStatuses.map { ($0.id, .unknown) }, at: 0)
|
||||||
|
|
||||||
if let newer = pagination?.newer {
|
if let newer = pagination?.newer {
|
||||||
self.newer = newer
|
self.newer = newer
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
|
||||||
let newIndexPaths = (0..<newStatuses.count).map {
|
DispatchQueue.main.async {
|
||||||
IndexPath(row: $0, section: 0)
|
let newIndexPaths = (0..<newStatuses.count).map {
|
||||||
|
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 the top)
|
||||||
|
self.tableView.scrollToRow(at: IndexPath(row: newStatuses.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 the top)
|
|
||||||
self.tableView.scrollToRow(at: IndexPath(row: newStatuses.count, section: 0), at: .top, animated: false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +182,7 @@ extension TimelineTableViewController: StatusTableViewCellDelegate {
|
||||||
extension TimelineTableViewController: UITableViewDataSourcePrefetching {
|
extension TimelineTableViewController: 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.cache.status(for: statusID(for: indexPath)) else { continue }
|
guard let status = mastodonController.persistentContainer.status(for: statusID(for: indexPath)) else { continue }
|
||||||
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
_ = ImageCache.avatars.get(status.account.avatar, completion: nil)
|
||||||
for attachment in status.attachments {
|
for attachment in status.attachments {
|
||||||
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
_ = ImageCache.attachments.get(attachment.url, completion: nil)
|
||||||
|
@ -185,7 +192,7 @@ extension TimelineTableViewController: 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.cache.status(for: statusID(for: indexPath)) else { continue }
|
guard let status = mastodonController.persistentContainer.status(for: statusID(for: indexPath)) else { continue }
|
||||||
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
||||||
for attachment in status.attachments {
|
for attachment in status.attachments {
|
||||||
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
||||||
|
|
|
@ -32,12 +32,13 @@ class UserActivityManager {
|
||||||
|
|
||||||
// MARK: - New Post
|
// MARK: - New Post
|
||||||
static func newPostActivity(mentioning: Account? = nil) -> NSUserActivity {
|
static func newPostActivity(mentioning: Account? = nil) -> NSUserActivity {
|
||||||
|
// todo: update to use managed objects
|
||||||
let activity = NSUserActivity(type: .newPost)
|
let activity = NSUserActivity(type: .newPost)
|
||||||
activity.isEligibleForPrediction = true
|
activity.isEligibleForPrediction = true
|
||||||
if let mentioning = mentioning {
|
if let mentioning = mentioning {
|
||||||
activity.userInfo = ["mentioning": mentioning.acct]
|
activity.userInfo = ["mentioning": mentioning.acct]
|
||||||
activity.title = "Send a message to \(mentioning.displayOrUserName)"
|
activity.title = "Send a message to \(mentioning.displayName)"
|
||||||
activity.suggestedInvocationPhrase = "Send a message to \(mentioning.displayOrUserName)"
|
activity.suggestedInvocationPhrase = "Send a message to \(mentioning.displayName)"
|
||||||
} else {
|
} else {
|
||||||
activity.userInfo = [:]
|
activity.userInfo = [:]
|
||||||
activity.title = "New Post"
|
activity.title = "New Post"
|
||||||
|
|
|
@ -48,7 +48,7 @@ class AttachmentsContainerView: UIView {
|
||||||
|
|
||||||
// MARK: - User Interaface
|
// MARK: - User Interaface
|
||||||
|
|
||||||
func updateUI(status: Status) {
|
func updateUI(status: StatusMO) {
|
||||||
self.statusID = status.id
|
self.statusID = status.id
|
||||||
attachments = status.attachments.filter { AttachmentsContainerView.supportedAttachmentTypes.contains($0.kind) }
|
attachments = status.attachments.filter { AttachmentsContainerView.supportedAttachmentTypes.contains($0.kind) }
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,15 @@ class EmojiLabel: UILabel {
|
||||||
|
|
||||||
extension EmojiLabel {
|
extension EmojiLabel {
|
||||||
func updateForAccountDisplayName(account: Account) {
|
func updateForAccountDisplayName(account: Account) {
|
||||||
|
if Preferences.shared.hideCustomEmojiInUsernames {
|
||||||
|
self.text = account.displayName
|
||||||
|
self.removeEmojis()
|
||||||
|
} else {
|
||||||
|
self.text = account.displayName
|
||||||
|
self.setEmojis(account.emojis, identifier: account.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func updateForAccountDisplayName(account: AccountMO) {
|
||||||
if Preferences.shared.hideCustomEmojiInUsernames {
|
if Preferences.shared.hideCustomEmojiInUsernames {
|
||||||
self.text = account.displayNameWithoutCustomEmoji
|
self.text = account.displayNameWithoutCustomEmoji
|
||||||
self.removeEmojis()
|
self.removeEmojis()
|
||||||
|
|
|
@ -138,13 +138,14 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
let peopleStr: String
|
let peopleStr: String
|
||||||
// todo: figure out how to localize this
|
// todo: figure out how to localize this
|
||||||
|
// todo: update to use managed objects
|
||||||
switch people.count {
|
switch people.count {
|
||||||
case 1:
|
case 1:
|
||||||
peopleStr = people.first!.displayOrUserName
|
peopleStr = people.first!.displayName
|
||||||
case 2:
|
case 2:
|
||||||
peopleStr = people.first!.displayOrUserName + " and " + people.last!.displayOrUserName
|
peopleStr = people.first!.displayName + " and " + people.last!.displayName
|
||||||
default:
|
default:
|
||||||
peopleStr = people.dropLast().map { $0.displayOrUserName }.joined(separator: ", ") + ", and " + people.last!.displayOrUserName
|
peopleStr = people.dropLast().map { $0.displayName }.joined(separator: ", ") + ", and " + people.last!.displayName
|
||||||
}
|
}
|
||||||
actionLabel.text = "\(verb) by \(peopleStr)"
|
actionLabel.text = "\(verb) by \(peopleStr)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,15 +72,16 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateActionLabel(people: [Account]) {
|
func updateActionLabel(people: [Account]) {
|
||||||
|
// 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 {
|
||||||
case 1:
|
case 1:
|
||||||
peopleStr = people.first!.displayOrUserName
|
peopleStr = people.first!.displayName
|
||||||
case 2:
|
case 2:
|
||||||
peopleStr = people.first!.displayOrUserName + " and " + people.last!.displayOrUserName
|
peopleStr = people.first!.displayName + " and " + people.last!.displayName
|
||||||
default:
|
default:
|
||||||
peopleStr = people.dropLast().map { $0.displayOrUserName }.joined(separator: ", ") + ", and " + people.last!.displayOrUserName
|
peopleStr = people.dropLast().map { $0.displayName }.joined(separator: ", ") + ", and " + people.last!.displayName
|
||||||
|
|
||||||
}
|
}
|
||||||
actionLabel.text = "Followed by \(peopleStr)"
|
actionLabel.text = "Followed by \(peopleStr)"
|
||||||
|
|
|
@ -52,12 +52,13 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(account: Account) {
|
func updateUI(account: Account) {
|
||||||
|
// todo: update to use managed objects
|
||||||
self.account = account
|
self.account = account
|
||||||
if Preferences.shared.hideCustomEmojiInUsernames {
|
if Preferences.shared.hideCustomEmojiInUsernames {
|
||||||
actionLabel.text = "Request to follow from \(account.displayNameWithoutCustomEmoji)"
|
actionLabel.text = "Request to follow from \(account.displayName)"
|
||||||
actionLabel.removeEmojis()
|
actionLabel.removeEmojis()
|
||||||
} else {
|
} else {
|
||||||
actionLabel.text = "Request to follow from \(account.displayOrUserName)"
|
actionLabel.text = "Request to follow from \(account.displayName)"
|
||||||
actionLabel.setEmojis(account.emojis, identifier: account.id)
|
actionLabel.setEmojis(account.emojis, identifier: account.id)
|
||||||
}
|
}
|
||||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||||
|
|
|
@ -95,25 +95,26 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
open func createObserversIfNecessary() {
|
open func createObserversIfNecessary() {
|
||||||
if statusUpdater == nil {
|
// todo: KVO on StatusMO for this?
|
||||||
statusUpdater = mastodonController.cache.statusSubject
|
// if statusUpdater == nil {
|
||||||
.filter { [unowned self] in $0.id == self.statusID }
|
// statusUpdater = mastodonController.cache.statusSubject
|
||||||
.receive(on: DispatchQueue.main)
|
// .filter { [unowned self] in $0.id == self.statusID }
|
||||||
.sink { [unowned self] in self.updateStatusState(status: $0) }
|
// .receive(on: DispatchQueue.main)
|
||||||
}
|
// .sink { [unowned self] in self.updateStatusState(status: $0) }
|
||||||
|
// }
|
||||||
if accountUpdater == nil {
|
//
|
||||||
accountUpdater = mastodonController.cache.accountSubject
|
// if accountUpdater == nil {
|
||||||
.filter { [unowned self] in $0.id == self.accountID }
|
// accountUpdater = mastodonController.cache.accountSubject
|
||||||
.receive(on: DispatchQueue.main)
|
// .filter { [unowned self] in $0.id == self.accountID }
|
||||||
.sink { [unowned self] in self.updateUI(account: $0) }
|
// .receive(on: DispatchQueue.main)
|
||||||
}
|
// .sink { [unowned self] in self.updateUI(account: $0) }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(statusID: String, state: StatusState) {
|
func updateUI(statusID: String, state: StatusState) {
|
||||||
createObserversIfNecessary()
|
createObserversIfNecessary()
|
||||||
|
|
||||||
guard let status = mastodonController.cache.status(for: statusID) else {
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||||
fatalError("Missing cached status")
|
fatalError("Missing cached status")
|
||||||
}
|
}
|
||||||
self.statusID = statusID
|
self.statusID = statusID
|
||||||
|
@ -161,9 +162,9 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStatusState(status: Status) {
|
func updateStatusState(status: StatusMO) {
|
||||||
favorited = status.favourited ?? false
|
favorited = status.favourited
|
||||||
reblogged = status.reblogged ?? false
|
reblogged = status.reblogged
|
||||||
|
|
||||||
if favorited {
|
if favorited {
|
||||||
favoriteButton.accessibilityLabel = NSLocalizedString("Undo Favorite", comment: "undo favorite button accessibility label")
|
favoriteButton.accessibilityLabel = NSLocalizedString("Undo Favorite", comment: "undo favorite button accessibility label")
|
||||||
|
@ -177,22 +178,22 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(account: Account) {
|
func updateUI(account: AccountMO) {
|
||||||
usernameLabel.text = "@\(account.acct)"
|
usernameLabel.text = "@\(account.acct)"
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in
|
||||||
guard let self = self, let data = data, self.accountID == account.id else { return }
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
guard let self = self, let data = data, self.accountID == account.id else { return }
|
||||||
self.avatarImageView.image = UIImage(data: data)
|
self.avatarImageView.image = UIImage(data: data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateUIForPreferences() {
|
@objc func updateUIForPreferences() {
|
||||||
guard let mastodonController = mastodonController, let account = mastodonController.cache.account(for: accountID) else { return }
|
guard let mastodonController = mastodonController, let account = mastodonController.persistentContainer.account(for: accountID) else { return }
|
||||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||||
displayNameLabel.updateForAccountDisplayName(account: account)
|
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||||
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.cache.status(for: statusID)?.sensitive ?? false)
|
attachmentsView.contentHidden = Preferences.shared.blurAllMedia || (mastodonController.persistentContainer.status(for: statusID)?.sensitive ?? false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
|
@ -311,7 +312,7 @@ class BaseStatusTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
extension BaseStatusTableViewCell: AttachmentViewDelegate {
|
extension BaseStatusTableViewCell: AttachmentViewDelegate {
|
||||||
func attachmentViewGallery(startingAt index: Int) -> UIViewController {
|
func attachmentViewGallery(startingAt index: Int) -> UIViewController {
|
||||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:))
|
let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:))
|
||||||
return delegate!.gallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index)
|
return delegate!.gallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
timestampAndClientLabel.text = timestampAndClientText
|
timestampAndClientLabel.text = timestampAndClientText
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateStatusState(status: Status) {
|
override func updateStatusState(status: StatusMO) {
|
||||||
super.updateStatusState(status: status)
|
super.updateStatusState(status: status)
|
||||||
|
|
||||||
// todo: localize me
|
// todo: localize me
|
||||||
|
@ -57,7 +57,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
totalReblogsButton.setTitle("\(status.reblogsCount) Reblog\(status.reblogsCount == 1 ? "" : "s")", for: .normal)
|
totalReblogsButton.setTitle("\(status.reblogsCount) Reblog\(status.reblogsCount == 1 ? "" : "s")", for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateUI(account: Account) {
|
override func updateUI(account: AccountMO) {
|
||||||
super.updateUI(account: account)
|
super.updateUI(account: account)
|
||||||
|
|
||||||
profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji
|
profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji
|
||||||
|
|
|
@ -47,20 +47,20 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
override func createObserversIfNecessary() {
|
override func createObserversIfNecessary() {
|
||||||
super.createObserversIfNecessary()
|
super.createObserversIfNecessary()
|
||||||
|
|
||||||
if rebloggerAccountUpdater == nil {
|
// todo: use KVO on reblogger account?
|
||||||
rebloggerAccountUpdater = mastodonController.cache.accountSubject
|
// if rebloggerAccountUpdater == nil {
|
||||||
.filter { [unowned self] in $0.id == self.rebloggerID }
|
// rebloggerAccountUpdater = mastodonController.cache.accountSubject
|
||||||
.receive(on: DispatchQueue.main)
|
// .filter { [unowned self] in $0.id == self.rebloggerID }
|
||||||
.sink { [unowned self] in self.updateRebloggerLabel(reblogger: $0) }
|
// .receive(on: DispatchQueue.main)
|
||||||
}
|
// .sink { [unowned self] in self.updateRebloggerLabel(reblogger: $0) }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateUI(statusID: String, state: StatusState) {
|
override func updateUI(statusID: String, state: StatusState) {
|
||||||
guard var status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
guard var status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
|
||||||
|
|
||||||
let realStatusID: String
|
let realStatusID: String
|
||||||
if let rebloggedStatusID = status.reblog?.id,
|
if let rebloggedStatus = status.reblog {
|
||||||
let rebloggedStatus = mastodonController.cache.status(for: rebloggedStatusID) {
|
|
||||||
reblogStatusID = statusID
|
reblogStatusID = statusID
|
||||||
rebloggerID = status.account.id
|
rebloggerID = status.account.id
|
||||||
status = rebloggedStatus
|
status = rebloggedStatus
|
||||||
|
@ -77,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
|
||||||
}
|
}
|
||||||
|
@ -85,12 +85,12 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
@objc override func updateUIForPreferences() {
|
@objc override func updateUIForPreferences() {
|
||||||
super.updateUIForPreferences()
|
super.updateUIForPreferences()
|
||||||
if let rebloggerID = rebloggerID,
|
if let rebloggerID = rebloggerID,
|
||||||
let reblogger = mastodonController.cache.account(for: rebloggerID) {
|
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||||
updateRebloggerLabel(reblogger: reblogger)
|
updateRebloggerLabel(reblogger: reblogger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateRebloggerLabel(reblogger: Account) {
|
private func updateRebloggerLabel(reblogger: AccountMO) {
|
||||||
if Preferences.shared.hideCustomEmojiInUsernames {
|
if Preferences.shared.hideCustomEmojiInUsernames {
|
||||||
reblogLabel.text = "Reblogged by \(reblogger.displayNameWithoutCustomEmoji)"
|
reblogLabel.text = "Reblogged by \(reblogger.displayNameWithoutCustomEmoji)"
|
||||||
reblogLabel.removeEmojis()
|
reblogLabel.removeEmojis()
|
||||||
|
@ -104,7 +104,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
||||||
// if the mastodonController is nil (i.e. the delegate is nil), then the screen this cell was a part of has been deallocated
|
// if the mastodonController is nil (i.e. the delegate is nil), then the screen this cell was a part of has been deallocated
|
||||||
// so we bail out immediately, since there's nothing to update
|
// so we bail out immediately, since there's nothing to update
|
||||||
guard let mastodonController = mastodonController else { return }
|
guard let mastodonController = mastodonController else { return }
|
||||||
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
|
||||||
|
|
||||||
timestampLabel.text = status.createdAt.timeAgoString()
|
timestampLabel.text = status.createdAt.timeAgoString()
|
||||||
timestampLabel.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())
|
timestampLabel.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())
|
||||||
|
|
|
@ -15,7 +15,7 @@ class StatusContentTextView: ContentTextView {
|
||||||
didSet {
|
didSet {
|
||||||
guard let statusID = statusID else { return }
|
guard let statusID = statusID else { return }
|
||||||
guard let mastodonController = mastodonController,
|
guard let mastodonController = mastodonController,
|
||||||
let status = mastodonController.cache.status(for: statusID) else {
|
let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||||
fatalError("Can't set StatusContentTextView text without cached status for \(statusID)")
|
fatalError("Can't set StatusContentTextView text without cached status for \(statusID)")
|
||||||
}
|
}
|
||||||
setTextFromHtml(status.content)
|
setTextFromHtml(status.content)
|
||||||
|
|
|
@ -314,7 +314,8 @@ struct XCBActions {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
show(vc)
|
show(vc)
|
||||||
}
|
}
|
||||||
let alertController = UIAlertController(title: "Follow \(account.displayNameWithoutCustomEmoji)?", message: nil, preferredStyle: .alert)
|
// todo: update to use managed objects
|
||||||
|
let alertController = UIAlertController(title: "Follow \(account.displayName)?", message: nil, preferredStyle: .alert)
|
||||||
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in
|
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) in
|
||||||
performAction(account)
|
performAction(account)
|
||||||
}))
|
}))
|
||||||
|
|
Loading…
Reference in New Issue