Make Account.avatar optional for gotosocial

This commit is contained in:
Shadowfacts 2022-02-16 22:12:47 -05:00
parent 80c79ded3b
commit de93d6e171
22 changed files with 170 additions and 144 deletions

View File

@ -20,8 +20,9 @@ public final class Account: AccountProtocol, Decodable {
public let statusesCount: Int public let statusesCount: Int
public let note: String public let note: String
public let url: URL public let url: URL
public let avatar: URL // required on mastodon, but optional on gotosocial
public let avatarStatic: URL public let avatar: URL?
public let avatarStatic: URL?
public let header: URL? public let header: URL?
public let headerStatic: URL? public let headerStatic: URL?
public private(set) var emojis: [Emoji] public private(set) var emojis: [Emoji]
@ -44,8 +45,8 @@ public final class Account: AccountProtocol, Decodable {
self.statusesCount = try container.decode(Int.self, forKey: .statusesCount) self.statusesCount = try container.decode(Int.self, forKey: .statusesCount)
self.note = try container.decode(String.self, forKey: .note) self.note = try container.decode(String.self, forKey: .note)
self.url = try container.decode(URL.self, forKey: .url) self.url = try container.decode(URL.self, forKey: .url)
self.avatar = try container.decode(URL.self, forKey: .avatar) self.avatar = try? container.decode(URL.self, forKey: .avatar)
self.avatarStatic = try container.decode(URL.self, forKey: .avatarStatic) self.avatarStatic = try? container.decode(URL.self, forKey: .avatarStatic)
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: .headerStatic) self.headerStatic = try? container.decode(URL.self, forKey: .headerStatic)
self.emojis = try container.decode([Emoji].self, forKey: .emojis) self.emojis = try container.decode([Emoji].self, forKey: .emojis)

View File

@ -22,7 +22,7 @@ public protocol AccountProtocol {
var statusesCount: Int { get } var statusesCount: Int { get }
var note: String { get } var note: String { get }
var url: URL { get } var url: URL { get }
var avatar: URL { get } var avatar: URL? { get }
var header: URL? { get } var header: URL? { get }
var moved: Bool? { get } var moved: Bool? { get }
var bot: Bool? { get } var bot: Bool? { get }

View File

@ -29,7 +29,8 @@ class AccountActivityItemSource: NSObject, UIActivityItemSource {
metadata.originalURL = account.url metadata.originalURL = account.url
metadata.url = account.url metadata.url = account.url
metadata.title = "\(account.displayName) (@\(account.username)@\(account.url.host!)" metadata.title = "\(account.displayName) (@\(account.username)@\(account.url.host!)"
if let data = ImageCache.avatars.getData(account.avatar), if let avatar = account.avatar,
let data = ImageCache.avatars.getData(avatar),
let image = UIImage(data: data) { let image = UIImage(data: data) {
metadata.iconProvider = NSItemProvider(object: image) metadata.iconProvider = NSItemProvider(object: image)
} }

View File

@ -32,7 +32,8 @@ class StatusActivityItemSource: NSObject, UIActivityItemSource {
let doc = try! SwiftSoup.parse(status.content) let doc = try! SwiftSoup.parse(status.content)
let content = try! doc.text() let content = try! doc.text()
metadata.title = "\(status.account.displayName): \"\(content)\"" metadata.title = "\(status.account.displayName): \"\(content)\""
if let data = ImageCache.avatars.getData(status.account.avatar), if let avatar = status.account.avatar,
let data = ImageCache.avatars.getData(avatar),
let image = UIImage(data: data) { let image = UIImage(data: data) {
metadata.iconProvider = NSItemProvider(object: image) metadata.iconProvider = NSItemProvider(object: image)
} }

View File

@ -19,7 +19,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 botCD: Bool
@NSManaged public var createdAt: Date @NSManaged public var createdAt: Date
@NSManaged public var displayName: String @NSManaged public var displayName: String

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21D49" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Account" representedClassName="AccountMO" syncable="YES"> <entity name="Account" representedClassName="AccountMO" syncable="YES">
<attribute name="acct" attributeType="String"/> <attribute name="acct" attributeType="String"/>
<attribute name="avatar" attributeType="URI"/> <attribute name="avatar" optional="YES" attributeType="URI"/>
<attribute name="botCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="botCD" 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"/>

View File

@ -189,7 +189,7 @@ struct ComposeAutocompleteMentionsView: View {
} }
} }
var avatar: URL { var avatar: URL? {
switch self { switch self {
case let .pachyderm(account): case let .pachyderm(account):
return account.avatar return account.avatar

View File

@ -86,7 +86,8 @@ class ExpandThreadTableViewCell: UITableViewCell {
xConstraint xConstraint
]) ])
let req = ImageCache.avatars.get(account.avatar) { [weak accountImageView] (_, image) in if let avatar = account.avatar {
let req = ImageCache.avatars.get(avatar) { [weak accountImageView] (_, image) in
DispatchQueue.main.async { DispatchQueue.main.async {
accountImageView?.image = image accountImageView?.image = image
} }
@ -96,6 +97,7 @@ class ExpandThreadTableViewCell: UITableViewCell {
} }
} }
} }
}
@objc private func preferencesChanged() { @objc private func preferencesChanged() {
avatarImageViews.forEach { avatarImageViews.forEach {

View File

@ -55,7 +55,8 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
noteTextView.setEmojis(account.emojis) noteTextView.setEmojis(account.emojis)
avatarImageView.image = nil avatarImageView.image = nil
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (_, image) in if let avatar = account.avatar {
avatarRequest = ImageCache.avatars.get(avatar) { [weak self] (_, image) in
defer { defer {
self?.avatarRequest = nil self?.avatarRequest = nil
} }
@ -68,6 +69,7 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
self.avatarImageView.image = image self.avatarImageView.image = image
} }
} }
}
headerImageView.image = nil headerImageView.image = nil
if let header = account.header { if let header = account.header {

View File

@ -86,8 +86,10 @@ class FastSwitchingAccountView: UIView {
let controller = MastodonController.getForAccount(account) let controller = MastodonController.getForAccount(account)
controller.getOwnAccount { [weak self] (result) in controller.getOwnAccount { [weak self] (result) in
guard let self = self, case let .success(account) = result else { return } guard let self = self,
self.avatarRequest = ImageCache.avatars.get(account.avatar) { [weak avatarImageView] (_, image) in case let .success(account) = result,
let avatar = account.avatar else { return }
self.avatarRequest = ImageCache.avatars.get(avatar) { [weak avatarImageView] (_, image) in
guard let avatarImageView = avatarImageView, let image = image else { return } guard let avatarImageView = avatarImageView, let image = image else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
avatarImageView.image = image avatarImageView.image = image

View File

@ -259,7 +259,8 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
for indexPath in indexPaths { for indexPath in indexPaths {
guard let group = dataSource.itemIdentifier(for: indexPath) else { continue } guard let group = dataSource.itemIdentifier(for: indexPath) else { continue }
for notification in group.notifications { for notification in group.notifications {
ImageCache.avatars.fetchIfNotCached(notification.account.avatar) guard let avatar = notification.account.avatar else { continue }
ImageCache.avatars.fetchIfNotCached(avatar)
} }
} }
} }
@ -268,7 +269,8 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
for indexPath in indexPaths { for indexPath in indexPaths {
guard let group = dataSource.itemIdentifier(for: indexPath) else { continue } guard let group = dataSource.itemIdentifier(for: indexPath) else { continue }
for notification in group.notifications { for notification in group.notifications {
ImageCache.avatars.cancelWithoutCallback(notification.account.avatar) guard let avatar = notification.account.avatar else { continue }
ImageCache.avatars.cancelWithoutCallback(avatar)
} }
} }
} }

View File

@ -37,8 +37,9 @@ struct LocalAccountAvatarView: View {
func loadImage() { func loadImage() {
let controller = MastodonController.getForAccount(localAccountInfo) let controller = MastodonController.getForAccount(localAccountInfo)
controller.getOwnAccount { (result) in controller.getOwnAccount { (result) in
guard case let .success(account) = result else { return } guard case let .success(account) = result,
_ = ImageCache.avatars.get(account.avatar) { (_, image) in let avatar = account.avatar else { return }
_ = ImageCache.avatars.get(avatar) { (_, image) in
DispatchQueue.main.async { DispatchQueue.main.async {
self.avatarImage = image self.avatarImage = image
} }

View File

@ -42,7 +42,7 @@ class MyProfileViewController: ProfileViewController {
} }
private func setAvatarTabBarImage<Account: AccountProtocol>(account: Account) { private func setAvatarTabBarImage<Account: AccountProtocol>(account: Account) {
let avatarURL = account.avatar guard let avatarURL = account.avatar else { return }
_ = ImageCache.avatars.get(avatarURL, completion: { [weak self] (_, image) in _ = ImageCache.avatars.get(avatarURL, completion: { [weak self] (_, image) in
guard let self = self, guard let self = self,
let image = image, let image = image,

View File

@ -21,7 +21,8 @@ extension StatusTablePrefetching {
return return
} }
for status in statuses { for status in statuses {
ImageCache.avatars.fetchIfNotCached(status.account.avatar) guard let avatar = status.account.avatar else { continue }
ImageCache.avatars.fetchIfNotCached(avatar)
for attachment in status.attachments where attachment.kind == .image { for attachment in status.attachments where attachment.kind == .image {
ImageCache.attachments.fetchIfNotCached(attachment.url) ImageCache.attachments.fetchIfNotCached(attachment.url)
} }
@ -36,7 +37,8 @@ extension StatusTablePrefetching {
return return
} }
for status in statuses { for status in statuses {
ImageCache.avatars.cancelWithoutCallback(status.account.avatar) guard let avatar = status.account.avatar else { continue }
ImageCache.avatars.cancelWithoutCallback(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)
} }

View File

@ -62,7 +62,7 @@ class AccountTableViewCell: UITableViewCell {
let accountID = self.accountID let accountID = self.accountID
let avatarURL = account.avatar if let avatarURL = account.avatar {
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
guard let self = self else { return } guard let self = self else { return }
self.avatarRequest = nil self.avatarRequest = nil
@ -75,6 +75,7 @@ class AccountTableViewCell: UITableViewCell {
self.avatarImageView.image = transformedImage self.avatarImageView.image = transformedImage
} }
} }
}
let doc = try! SwiftSoup.parse(account.note) let doc = try! SwiftSoup.parse(account.note)
noteLabel.text = try! doc.text() noteLabel.text = try! doc.text()

View File

@ -69,7 +69,8 @@ class LargeAccountDetailView: UIView {
displayNameLabel.updateForAccountDisplayName(account: account) displayNameLabel.updateForAccountDisplayName(account: account)
usernameLabel.text = "@\(account.acct)" usernameLabel.text = "@\(account.acct)"
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (_, image) in if let avatar = account.avatar {
avatarRequest = ImageCache.avatars.get(avatar) { [weak self] (_, image) in
guard let self = self, let image = image else { return } guard let self = self, let image = image else { return }
self.avatarRequest = nil self.avatarRequest = nil
DispatchQueue.main.async { DispatchQueue.main.async {
@ -77,5 +78,6 @@ class LargeAccountDetailView: UIView {
} }
} }
} }
}
} }

View File

@ -83,7 +83,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
imageView.translatesAutoresizingMaskIntoConstraints = false imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.masksToBounds = true imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30 imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
let avatarURL = account.avatar if let avatarURL = account.avatar {
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
guard let self = self else { return } guard let self = self else { return }
guard let image = image, guard let image = image,
@ -100,6 +100,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
imageView.image = transformedImage imageView.image = transformedImage
} }
} }
}
actionAvatarStackView.addArrangedSubview(imageView) actionAvatarStackView.addArrangedSubview(imageView)
imageViews.append(imageView) imageViews.append(imageView)
@ -131,7 +132,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
continue continue
} }
let avatarURL = account.avatar if let avatarURL = account.avatar {
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
guard let self = self else { return } guard let self = self else { return }
guard let image = image, guard let image = image,
@ -150,6 +151,7 @@ class ActionNotificationGroupTableViewCell: UITableViewCell {
} }
} }
} }
}
private func updateTimestamp() { private func updateTimestamp() {
guard let notification = group.notifications.first else { guard let notification = group.notifications.first else {

View File

@ -64,7 +64,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
imageView.translatesAutoresizingMaskIntoConstraints = false imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.masksToBounds = true imageView.layer.masksToBounds = true
imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30 imageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
let avatarURL = account.avatar if let avatarURL = account.avatar {
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
guard let self = self, guard let self = self,
let image = image, let image = image,
@ -78,6 +78,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
imageView.image = transformedImage imageView.image = transformedImage
} }
} }
}
avatarStackView.addArrangedSubview(imageView) avatarStackView.addArrangedSubview(imageView)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalToConstant: 30), imageView.widthAnchor.constraint(equalToConstant: 30),
@ -98,7 +99,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
continue continue
} }
let avatarURL = account.avatar if let avatarURL = account.avatar {
avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in avatarRequests[account.id] = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
guard let self = self else { return } guard let self = self else { return }
guard let image = image, guard let image = image,
@ -117,6 +118,7 @@ class FollowNotificationGroupTableViewCell: UITableViewCell {
} }
} }
} }
}
func updateActionLabel(names: [NSAttributedString]) -> NSAttributedString { func updateActionLabel(names: [NSAttributedString]) -> NSAttributedString {
// todo: figure out how to localize this // todo: figure out how to localize this

View File

@ -67,7 +67,8 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
actionLabel.text = "Request to follow from \(account.displayName)" actionLabel.text = "Request to follow from \(account.displayName)"
actionLabel.setEmojis(account.emojis, identifier: account.id) actionLabel.setEmojis(account.emojis, identifier: account.id)
} }
let avatarURL = account.avatar
if let avatarURL = account.avatar {
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
guard let self = self else { return } guard let self = self else { return }
self.avatarRequest = nil self.avatarRequest = nil
@ -83,6 +84,7 @@ class FollowRequestNotificationTableViewCell: UITableViewCell {
} }
} }
} }
}
private func updateTimestamp() { private func updateTimestamp() {
guard let notification = notification else { return } guard let notification = notification else { return }

View File

@ -202,7 +202,7 @@ class ProfileHeaderView: UIView {
isGrayscale = Preferences.shared.grayscaleImages isGrayscale = Preferences.shared.grayscaleImages
let accountID = account.id let accountID = account.id
let avatarURL = account.avatar if let avatarURL = account.avatar {
// always load original for avatars, because ImageCache.avatars stores them scaled-down in memory // always load original for avatars, because ImageCache.avatars stores them scaled-down in memory
avatarRequest = ImageCache.avatars.get(avatarURL, loadOriginal: true) { [weak self] (_, image) in avatarRequest = ImageCache.avatars.get(avatarURL, loadOriginal: true) { [weak self] (_, image) in
guard let self = self, guard let self = self,
@ -220,6 +220,7 @@ class ProfileHeaderView: UIView {
self.avatarImageView.image = transformedImage self.avatarImageView.image = transformedImage
} }
} }
}
if let header = account.header { if let header = account.header {
headerRequest = ImageCache.headers.get(header) { [weak self] (_, image) in headerRequest = ImageCache.headers.get(header) { [weak self] (_, image) in
guard let self = self, guard let self = self,
@ -243,10 +244,11 @@ class ProfileHeaderView: UIView {
// MARK: Interaction // MARK: Interaction
@objc func avatarPressed() { @objc func avatarPressed() {
guard let account = mastodonController.persistentContainer.account(for: accountID) else { guard let account = mastodonController.persistentContainer.account(for: accountID),
let avatar = account.avatar else {
return return
} }
delegate?.showLoadingLargeImage(url: account.avatar, cache: .avatars, description: nil, animatingFrom: avatarImageView) delegate?.showLoadingLargeImage(url: avatar, cache: .avatars, description: nil, animatingFrom: avatarImageView)
} }
@objc func headerPressed() { @objc func headerPressed() {

View File

@ -251,8 +251,8 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
func updateGrayscaleableUI(account: AccountMO, status: StatusMO) { func updateGrayscaleableUI(account: AccountMO, status: StatusMO) {
isGrayscale = Preferences.shared.grayscaleImages isGrayscale = Preferences.shared.grayscaleImages
let avatarURL = account.avatar
let accountID = account.id let accountID = account.id
if let avatarURL = account.avatar {
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
guard let self = self, guard let self = self,
let image = image, let image = image,
@ -263,6 +263,7 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider {
self.avatarImageView.image = transformedImage self.avatarImageView.image = transformedImage
} }
} }
}
if contentTextView.hasEmojis { if contentTextView.hasEmojis {
contentTextView.setTextFrom(status: status) contentTextView.setTextFrom(status: status)

View File

@ -269,7 +269,7 @@ struct XCBActions {
"followers": account.followersCount.description, "followers": account.followersCount.description,
"following": account.followingCount.description, "following": account.followingCount.description,
"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,7 +284,7 @@ struct XCBActions {
"followers": account.followersCount.description, "followers": account.followersCount.description,
"following": account.followingCount.description, "following": account.followingCount.description,
"url": account.url.absoluteString, "url": account.url.absoluteString,
"avatarURL": account.avatar.absoluteString, "avatarURL": account.avatar?.absoluteString,
"headerURL": account.header?.absoluteString "headerURL": account.header?.absoluteString
]) ])
} }