Compare commits

...

3 Commits

Author SHA1 Message Date
Shadowfacts 2c8ba878b7
Start converting UI to use CoreData backed objects instead of API
objects directly
2020-04-12 12:54:27 -04:00
Shadowfacts a0e95d4577
Remove unnecessary attachment decoding code
For some reason, creating a URL from a string decoded from the container
was producing URL objects that could not be round-tripped through
PropertyListEncoder/Decoder. Decoding a URL directly from the container
works correctly.
2020-04-12 12:52:51 -04:00
Shadowfacts 465aedd43f
Make account info username optional
Onboarding view controller needs to set the account info object on the
mastodon controller before calling getOwnAccount since getOwnAccount
will upsert the user's account into the persistent container, which
requires the account info to exist to create a unique-per-account
identifier.
2020-04-12 11:14:10 -04:00
25 changed files with 173 additions and 124 deletions

View File

@ -29,18 +29,10 @@ public class Attachment: Codable {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.kind = try container.decode(Kind.self, forKey: .kind)
self.url = URL(string: try container.decode(String.self, forKey: .url))!
self.previewURL = URL(string: try container.decode(String.self, forKey: .previewURL))!
if let remote = try? container.decode(String.self, forKey: .remoteURL) {
self.remoteURL = URL(string: remote)!
} else {
self.remoteURL = nil
}
if let text = try? container.decode(String.self, forKey: .textURL) {
self.textURL = URL(string: text)!
} else {
self.textURL = nil
}
self.url = try container.decode(URL.self, forKey: .url)
self.previewURL = try container.decode(URL.self, forKey: .previewURL)
self.remoteURL = try? container.decode(URL.self, forKey: .remoteURL)
self.textURL = try? container.decode(URL.self, forKey: .textURL)
self.meta = try? container.decode(Metadata.self, forKey: .meta)
self.description = try? container.decode(String.self, forKey: .description)
}

View File

@ -33,9 +33,9 @@ class MastodonController {
private(set) lazy var cache = MastodonCache(mastodonController: self)
private(set) lazy var persistentContainer = MastodonCachePersistentStore(for: self)
let instanceURL: URL
private(set) var accountInfo: LocalData.UserAccountInfo?
var accountInfo: LocalData.UserAccountInfo?
let client: Client!

View File

@ -58,7 +58,7 @@ extension AccountMO {
}
self.acct = account.acct
self.avatar = account.avatar
self.avatar = account.avatarStatic // we don't animate avatars
self.bot = account.bot ?? false
self.createdAt = account.createdAt
self.displayName = account.displayName
@ -66,7 +66,7 @@ extension AccountMO {
self.fields = account.fields ?? []
self.followersCount = account.followersCount
self.followingCount = account.followingCount
self.header = account.header
self.header = account.headerStatic // we don't animate headers
self.id = account.id
self.locked = account.locked
self.moved = account.moved ?? false

View File

@ -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) {
backgroundContext.perform {
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)
}
self.upsert(status: status)
if save, self.backgroundContext.hasChanges {
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? {
let context = context ?? viewContext
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) {
backgroundContext.perform {
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)
}
self.upsert(account: account)
if save, self.backgroundContext.hasChanges {
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?()
}
}
}

View File

@ -36,6 +36,7 @@ public final class StatusMO: NSManagedObject {
@NSManaged public var reblogged: Bool
@NSManaged public var reblogsCount: Int
@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 url: URL?
@NSManaged private var visibilityString: String
@ -95,6 +96,7 @@ extension StatusMO {
self.reblogged = status.reblogged ?? false
self.reblogsCount = status.reblogsCount
self.sensitive = status.sensitive
self.spoilerText = status.spoilerText
self.uri = status.uri
self.visibility = status.visibility
self.account = container.account(for: status.account.id, in: context) ?? AccountMO(apiAccount: status.account, container: container, context: context)

View File

@ -44,6 +44,7 @@
<attribute name="reblogged" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="reblogsCount" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sensitive" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="spoilerText" attributeType="String"/>
<attribute name="uri" attributeType="String"/>
<attribute name="url" optional="YES" attributeType="URI"/>
<attribute name="visibilityString" attributeType="String"/>
@ -57,6 +58,6 @@
</entity>
<elements>
<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>
</model>

View File

@ -9,7 +9,7 @@
import Foundation
import Pachyderm
extension Account {
extension AccountMO {
var displayOrUserName: String {
if displayName.isEmpty {
@ -31,7 +31,7 @@ extension Account {
private func stripCustomEmoji(from string: String) -> String {
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: "")
}
}

View File

@ -11,6 +11,7 @@ import Foundation
private let decoder = PropertyListDecoder()
private let encoder = PropertyListEncoder()
// todo: invalidate cache on underlying data change using KVO?
@propertyWrapper
struct LazilyDecoding<Enclosing, Value: Codable> {

View File

@ -44,11 +44,10 @@ class LocalData: ObservableObject {
let url = URL(string: instanceURL),
let clientId = info["clientID"],
let secret = info["clientSecret"],
let username = info["username"],
let accessToken = info["accessToken"] else {
return nil
}
return UserAccountInfo(id: id, instanceURL: url, clientID: clientId, clientSecret: secret, username: username, accessToken: accessToken)
return UserAccountInfo(id: id, instanceURL: url, clientID: clientId, clientSecret: secret, username: info["username"], accessToken: accessToken)
}
} else {
return []
@ -56,15 +55,18 @@ class LocalData: ObservableObject {
}
set {
objectWillChange.send()
let array = newValue.map { (info) in
return [
let array = newValue.map { (info) -> [String: String] in
var res = [
"id": info.id,
"instanceURL": info.instanceURL.absoluteString,
"clientID": info.clientID,
"clientSecret": info.clientSecret,
"username": info.username,
"accessToken": info.accessToken
]
if let username = info.username {
res["username"] = username
}
return res
}
defaults.set(array, forKey: accountsKey)
}
@ -85,7 +87,7 @@ class LocalData: ObservableObject {
return !accounts.isEmpty
}
func addAccount(instanceURL url: URL, clientID: String, clientSecret secret: String, username: String, accessToken: String) -> UserAccountInfo {
func addAccount(instanceURL url: URL, clientID: String, clientSecret secret: String, username: String?, accessToken: String) -> UserAccountInfo {
var accounts = self.accounts
if let index = accounts.firstIndex(where: { $0.instanceURL == url && $0.username == username }) {
accounts.remove(at: index)
@ -97,6 +99,13 @@ class LocalData: ObservableObject {
return info
}
func setUsername(for info: UserAccountInfo, username: String) {
var info = info
info.username = username
removeAccount(info)
accounts.append(info)
}
func removeAccount(_ info: UserAccountInfo) {
accounts.removeAll(where: { $0.id == info.id })
}
@ -128,7 +137,7 @@ extension LocalData {
let instanceURL: URL
let clientID: String
let clientSecret: String
let username: String
fileprivate(set) var username: String!
let accessToken: String
func hash(into hasher: inout Hasher) {

View File

@ -64,12 +64,7 @@ class MastodonCache {
func addAll(statuses: [Status]) {
statuses.forEach(add)
if let container = mastodonController?.persistentContainer {
statuses.forEach { container.addOrUpdate(status: $0, save: false) }
container.backgroundContext.perform {
try! container.backgroundContext.save()
}
}
mastodonController?.persistentContainer.addAll(statuses: statuses)
}
// MARK: - Accounts
@ -104,12 +99,7 @@ class MastodonCache {
func addAll(accounts: [Account]) {
accounts.forEach(add)
if let container = mastodonController?.persistentContainer {
accounts.forEach { container.addOrUpdate(account: $0, save: false) }
container.backgroundContext.perform {
try! container.backgroundContext.save()
}
}
mastodonController?.persistentContainer.addAll(accounts: accounts)
}
// MARK: - Relationships

View File

@ -213,7 +213,8 @@ class ComposeViewController: UIViewController {
replyAvatarImageViewTopConstraint!.isActive = true
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) {

View File

@ -68,10 +68,13 @@ extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate
let authCode = item.value else { return }
mastodonController.authorize(authorizationCode: authCode) { (accessToken) in
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: nil, accessToken: accessToken)
mastodonController.accountInfo = accountInfo
mastodonController.getOwnAccount { (account) in
LocalData.shared.setUsername(for: accountInfo, username: account.username)
DispatchQueue.main.async {
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken)
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
}
}

View File

@ -128,7 +128,7 @@ class ProfileTableViewController: EnhancedTableViewController {
}
@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
}

View File

@ -59,12 +59,15 @@ class TimelineTableViewController: EnhancedTableViewController {
let request = Client.getStatuses(timeline: timeline)
mastodonController.run(request) { response in
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.newer = pagination?.newer
self.older = pagination?.older
DispatchQueue.main.async {
self.tableView.reloadData()
self.mastodonController.persistentContainer.addAll(statuses: statuses) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
@ -101,13 +104,15 @@ class TimelineTableViewController: EnhancedTableViewController {
mastodonController.run(request) { response in
guard case let .success(newStatuses, pagination) = response else { fatalError() }
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 newIndexPaths = newRows.map { IndexPath(row: $0, section: self.timelineSegments.count - 1) }
self.timelineSegments[self.timelineSegments.count - 1].append(contentsOf: newStatuses.map { ($0.id, .unknown) })
DispatchQueue.main.async {
UIView.performWithoutAnimation {
self.tableView.insertRows(at: newIndexPaths, with: .none)
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
DispatchQueue.main.async {
UIView.performWithoutAnimation {
self.tableView.insertRows(at: newIndexPaths, with: .none)
}
}
}
}
@ -133,25 +138,27 @@ class TimelineTableViewController: EnhancedTableViewController {
mastodonController.run(request) { response in
guard case let .success(newStatuses, pagination) = response else { fatalError() }
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)
if let newer = pagination?.newer {
self.newer = newer
}
DispatchQueue.main.async {
let newIndexPaths = (0..<newStatuses.count).map {
IndexPath(row: $0, section: 0)
self.mastodonController.persistentContainer.addAll(statuses: newStatuses) {
DispatchQueue.main.async {
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 {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
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)
for attachment in status.attachments {
_ = ImageCache.attachments.get(attachment.url, completion: nil)
@ -185,7 +192,7 @@ extension TimelineTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
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)
for attachment in status.attachments {
ImageCache.attachments.cancelWithoutCallback(attachment.url)

View File

@ -32,12 +32,13 @@ class UserActivityManager {
// MARK: - New Post
static func newPostActivity(mentioning: Account? = nil) -> NSUserActivity {
// todo: update to use managed objects
let activity = NSUserActivity(type: .newPost)
activity.isEligibleForPrediction = true
if let mentioning = mentioning {
activity.userInfo = ["mentioning": mentioning.acct]
activity.title = "Send a message to \(mentioning.displayOrUserName)"
activity.suggestedInvocationPhrase = "Send a message to \(mentioning.displayOrUserName)"
activity.title = "Send a message to \(mentioning.displayName)"
activity.suggestedInvocationPhrase = "Send a message to \(mentioning.displayName)"
} else {
activity.userInfo = [:]
activity.title = "New Post"

View File

@ -48,7 +48,7 @@ class AttachmentsContainerView: UIView {
// MARK: - User Interaface
func updateUI(status: Status) {
func updateUI(status: StatusMO) {
self.statusID = status.id
attachments = status.attachments.filter { AttachmentsContainerView.supportedAttachmentTypes.contains($0.kind) }

View File

@ -83,6 +83,15 @@ class EmojiLabel: UILabel {
extension EmojiLabel {
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 {
self.text = account.displayNameWithoutCustomEmoji
self.removeEmojis()

View File

@ -138,13 +138,14 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
}
let peopleStr: String
// todo: figure out how to localize this
// todo: update to use managed objects
switch people.count {
case 1:
peopleStr = people.first!.displayOrUserName
peopleStr = people.first!.displayName
case 2:
peopleStr = people.first!.displayOrUserName + " and " + people.last!.displayOrUserName
peopleStr = people.first!.displayName + " and " + people.last!.displayName
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)"
}

View File

@ -72,15 +72,16 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
}
func updateActionLabel(people: [Account]) {
// todo: update to use managed objects
// todo: figure out how to localize this
let peopleStr: String
switch people.count {
case 1:
peopleStr = people.first!.displayOrUserName
peopleStr = people.first!.displayName
case 2:
peopleStr = people.first!.displayOrUserName + " and " + people.last!.displayOrUserName
peopleStr = people.first!.displayName + " and " + people.last!.displayName
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)"

View File

@ -52,12 +52,13 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
}
func updateUI(account: Account) {
// todo: update to use managed objects
self.account = account
if Preferences.shared.hideCustomEmojiInUsernames {
actionLabel.text = "Request to follow from \(account.displayNameWithoutCustomEmoji)"
actionLabel.text = "Request to follow from \(account.displayName)"
actionLabel.removeEmojis()
} else {
actionLabel.text = "Request to follow from \(account.displayOrUserName)"
actionLabel.text = "Request to follow from \(account.displayName)"
actionLabel.setEmojis(account.emojis, identifier: account.id)
}
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (data) in

View File

@ -95,25 +95,26 @@ class BaseStatusTableViewCell: UITableViewCell {
}
open func createObserversIfNecessary() {
if statusUpdater == nil {
statusUpdater = mastodonController.cache.statusSubject
.filter { [unowned self] in $0.id == self.statusID }
.receive(on: DispatchQueue.main)
.sink { [unowned self] in self.updateStatusState(status: $0) }
}
if accountUpdater == nil {
accountUpdater = mastodonController.cache.accountSubject
.filter { [unowned self] in $0.id == self.accountID }
.receive(on: DispatchQueue.main)
.sink { [unowned self] in self.updateUI(account: $0) }
}
// todo: KVO on StatusMO for this?
// if statusUpdater == nil {
// statusUpdater = mastodonController.cache.statusSubject
// .filter { [unowned self] in $0.id == self.statusID }
// .receive(on: DispatchQueue.main)
// .sink { [unowned self] in self.updateStatusState(status: $0) }
// }
//
// if accountUpdater == nil {
// accountUpdater = mastodonController.cache.accountSubject
// .filter { [unowned self] in $0.id == self.accountID }
// .receive(on: DispatchQueue.main)
// .sink { [unowned self] in self.updateUI(account: $0) }
// }
}
func updateUI(statusID: String, state: StatusState) {
createObserversIfNecessary()
guard let status = mastodonController.cache.status(for: statusID) else {
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
fatalError("Missing cached status")
}
self.statusID = statusID
@ -161,9 +162,9 @@ class BaseStatusTableViewCell: UITableViewCell {
}
}
func updateStatusState(status: Status) {
favorited = status.favourited ?? false
reblogged = status.reblogged ?? false
func updateStatusState(status: StatusMO) {
favorited = status.favourited
reblogged = status.reblogged
if favorited {
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)"
avatarImageView.image = nil
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 {
guard let self = self, let data = data, self.accountID == account.id else { return }
self.avatarImageView.image = UIImage(data: data)
}
}
}
@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)
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() {
@ -311,7 +312,7 @@ class BaseStatusTableViewCell: UITableViewCell {
extension BaseStatusTableViewCell: AttachmentViewDelegate {
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:))
return delegate!.gallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index)
}

View File

@ -49,7 +49,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
timestampAndClientLabel.text = timestampAndClientText
}
override func updateStatusState(status: Status) {
override func updateStatusState(status: StatusMO) {
super.updateStatusState(status: status)
// todo: localize me
@ -57,7 +57,7 @@ class ConversationMainStatusTableViewCell: BaseStatusTableViewCell {
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)
profileAccessibilityElement.accessibilityLabel = account.displayNameWithoutCustomEmoji

View File

@ -47,20 +47,20 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
override func createObserversIfNecessary() {
super.createObserversIfNecessary()
if rebloggerAccountUpdater == nil {
rebloggerAccountUpdater = mastodonController.cache.accountSubject
.filter { [unowned self] in $0.id == self.rebloggerID }
.receive(on: DispatchQueue.main)
.sink { [unowned self] in self.updateRebloggerLabel(reblogger: $0) }
}
// todo: use KVO on reblogger account?
// if rebloggerAccountUpdater == nil {
// rebloggerAccountUpdater = mastodonController.cache.accountSubject
// .filter { [unowned self] in $0.id == self.rebloggerID }
// .receive(on: DispatchQueue.main)
// .sink { [unowned self] in self.updateRebloggerLabel(reblogger: $0) }
// }
}
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
if let rebloggedStatusID = status.reblog?.id,
let rebloggedStatus = mastodonController.cache.status(for: rebloggedStatusID) {
if let rebloggedStatus = status.reblog {
reblogStatusID = statusID
rebloggerID = status.account.id
status = rebloggedStatus
@ -77,7 +77,7 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
updateTimestamp()
let pinned = status.pinned ?? false
let pinned = status.pinned
pinImageView.isHidden = !(pinned && showPinned)
timestampLabel.isHidden = !pinImageView.isHidden
}
@ -85,12 +85,12 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
@objc override func updateUIForPreferences() {
super.updateUIForPreferences()
if let rebloggerID = rebloggerID,
let reblogger = mastodonController.cache.account(for: rebloggerID) {
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
updateRebloggerLabel(reblogger: reblogger)
}
}
private func updateRebloggerLabel(reblogger: Account) {
private func updateRebloggerLabel(reblogger: AccountMO) {
if Preferences.shared.hideCustomEmojiInUsernames {
reblogLabel.text = "Reblogged by \(reblogger.displayNameWithoutCustomEmoji)"
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
// so we bail out immediately, since there's nothing to update
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.accessibilityLabel = TimelineStatusTableViewCell.relativeDateFormatter.localizedString(for: status.createdAt, relativeTo: Date())

View File

@ -15,7 +15,7 @@ class StatusContentTextView: ContentTextView {
didSet {
guard let statusID = statusID else { return }
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)")
}
setTextFromHtml(status.content)

View File

@ -314,7 +314,8 @@ struct XCBActions {
DispatchQueue.main.async {
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
performAction(account)
}))