Escape unicode in all URLs from API

This commit is contained in:
Justin Mazzocchi 2021-03-28 23:04:14 -07:00
parent 0ca2bf9653
commit 9552305a78
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
37 changed files with 136 additions and 107 deletions

View file

@ -16,10 +16,10 @@ struct AccountRecord: ContentDatabaseRecord, Hashable {
let statusesCount: Int let statusesCount: Int
let note: HTML let note: HTML
let url: String let url: String
let avatar: URL let avatar: UnicodeURL
let avatarStatic: URL let avatarStatic: UnicodeURL
let header: URL let header: UnicodeURL
let headerStatic: URL let headerStatic: UnicodeURL
let fields: [Account.Field] let fields: [Account.Field]
let emojis: [Emoji] let emojis: [Emoji]
let bot: Bool let bot: Bool

View file

@ -7,7 +7,7 @@ import Mastodon
struct FeaturedTagRecord: ContentDatabaseRecord, Hashable { struct FeaturedTagRecord: ContentDatabaseRecord, Hashable {
let id: FeaturedTag.Id let id: FeaturedTag.Id
let name: String let name: String
let url: URL let url: UnicodeURL
let statusesCount: Int let statusesCount: Int
let lastStatusAt: Date let lastStatusAt: Date
let accountId: Account.Id let accountId: Account.Id

View file

@ -8,8 +8,8 @@ struct IdentityProofRecord: ContentDatabaseRecord, Hashable {
let accountId: Account.Id let accountId: Account.Id
let provider: String let provider: String
let providerUsername: String let providerUsername: String
let profileUrl: URL let profileUrl: UnicodeURL
let proofUrl: URL let proofUrl: UnicodeURL
let updatedAt: Date let updatedAt: Date
} }

View file

@ -17,7 +17,7 @@ struct InstanceRecord: ContentDatabaseRecord, Hashable {
let invitesEnabled: Bool let invitesEnabled: Bool
let urls: Instance.URLs let urls: Instance.URLs
let stats: Instance.Stats let stats: Instance.Stats
let thumbnail: URL? let thumbnail: UnicodeURL?
let contactAccountId: Account.Id? let contactAccountId: Account.Id?
let maxTootChars: Int? let maxTootChars: Int?
} }

View file

@ -21,9 +21,9 @@ public extension Identity {
struct Instance: Codable, Hashable { struct Instance: Codable, Hashable {
public let uri: String public let uri: String
public let streamingAPI: URL public let streamingAPI: UnicodeURL
public let title: String public let title: String
public let thumbnail: URL? public let thumbnail: UnicodeURL?
public let version: String public let version: String
public let maxTootChars: Int? public let maxTootChars: Int?
} }
@ -34,10 +34,10 @@ public extension Identity {
public let username: String public let username: String
public let displayName: String public let displayName: String
public let url: String public let url: String
public let avatar: URL public let avatar: UnicodeURL
public let avatarStatic: URL public let avatarStatic: UnicodeURL
public let header: URL public let header: UnicodeURL
public let headerStatic: URL public let headerStatic: UnicodeURL
public let emojis: [Emoji] public let emojis: [Emoji]
public let followRequestCount: Int public let followRequestCount: Int
} }
@ -59,7 +59,7 @@ public extension Identity {
return instance?.title ?? url.host ?? url.absoluteString return instance?.title ?? url.host ?? url.absoluteString
} }
var image: URL? { account?.avatar ?? instance?.thumbnail } var image: URL? { (account?.avatar ?? instance?.thumbnail)?.url }
} }
public extension Identity.Preferences { public extension Identity.Preferences {

View file

@ -89,15 +89,14 @@ extension CollectionItem {
private extension Account { private extension Account {
func mediaPrefetchURLs(identityContext: IdentityContext) -> Set<URL> { func mediaPrefetchURLs(identityContext: IdentityContext) -> Set<URL> {
var urls = Set(emojis.compactMap { var urls = Set(emojis.map {
identityContext.appPreferences.animateCustomEmojis ? $0.url : $0.staticUrl (identityContext.appPreferences.animateCustomEmojis ? $0.url : $0.staticUrl).url
} })
.compactMap(URL.init(string:)))
if identityContext.appPreferences.animateAvatars == .everywhere { if identityContext.appPreferences.animateAvatars == .everywhere {
urls.insert(avatar) urls.insert(avatar.url)
} else { } else {
urls.insert(avatarStatic) urls.insert(avatarStatic.url)
} }
return urls return urls
@ -107,10 +106,9 @@ private extension Account {
private extension Status { private extension Status {
func mediaPrefetchURLs(identityContext: IdentityContext) -> Set<URL> { func mediaPrefetchURLs(identityContext: IdentityContext) -> Set<URL> {
displayStatus.account.mediaPrefetchURLs(identityContext: identityContext) displayStatus.account.mediaPrefetchURLs(identityContext: identityContext)
.union(displayStatus.mediaAttachments.compactMap(\.previewUrl)) .union(displayStatus.mediaAttachments.compactMap(\.previewUrl?.url))
.union(displayStatus.emojis.compactMap { .union(displayStatus.emojis.map {
identityContext.appPreferences.animateCustomEmojis ? $0.url : $0.staticUrl (identityContext.appPreferences.animateCustomEmojis ? $0.url : $0.staticUrl).url
} })
.compactMap(URL.init(string:)))
} }
} }

View file

@ -12,15 +12,12 @@ extension NSMutableAttributedString {
while let tokenRange = string.range(of: token) { while let tokenRange = string.range(of: token) {
let attachment = AnimatedTextAttachment() let attachment = AnimatedTextAttachment()
let imageURL: URL? let imageURL: URL
if identityContext.appPreferences.animateCustomEmojis, if identityContext.appPreferences.animateCustomEmojis {
let urlString = emoji.url { imageURL = emoji.url.url
imageURL = URL(stringEscapingPath: urlString)
} else if let staticURLString = emoji.staticUrl {
imageURL = URL(stringEscapingPath: staticURLString)
} else { } else {
imageURL = nil imageURL = emoji.staticUrl.url
} }
attachment.imageView.sd_setImage(with: imageURL) { image, _, _, _ in attachment.imageView.sd_setImage(with: imageURL) { image, _, _, _ in

View file

@ -14,10 +14,10 @@ public final class Account: Codable, Identifiable {
public let statusesCount: Int public let statusesCount: Int
public let note: HTML public let note: HTML
public let url: String public let url: String
public let avatar: URL public let avatar: UnicodeURL
public let avatarStatic: URL public let avatarStatic: UnicodeURL
public let header: URL public let header: UnicodeURL
public let headerStatic: URL public let headerStatic: UnicodeURL
public let fields: [Field] public let fields: [Field]
public let emojis: [Emoji] public let emojis: [Emoji]
@DecodableDefault.False public private(set) var bot: Bool @DecodableDefault.False public private(set) var bot: Bool
@ -36,10 +36,10 @@ public final class Account: Codable, Identifiable {
statusesCount: Int, statusesCount: Int,
note: HTML, note: HTML,
url: String, url: String,
avatar: URL, avatar: UnicodeURL,
avatarStatic: URL, avatarStatic: UnicodeURL,
header: URL, header: UnicodeURL,
headerStatic: URL, headerStatic: UnicodeURL,
fields: [Account.Field], fields: [Account.Field],
emojis: [Emoji], emojis: [Emoji],
bot: Bool, bot: Bool,

View file

@ -6,6 +6,6 @@ public struct AnnouncementReaction: Codable, Hashable {
public let name: String public let name: String
public let count: Int public let count: Int
public let me: Bool public let me: Bool
public let url: URL? public let url: UnicodeURL?
public let staticUrl: URL? public let staticUrl: UnicodeURL?
} }

View file

@ -34,9 +34,9 @@ public struct Attachment: Codable, Hashable {
public let id: Id public let id: Id
public let type: AttachmentType public let type: AttachmentType
public let url: URL public let url: UnicodeURL
public let remoteUrl: URL? public let remoteUrl: UnicodeURL?
public let previewUrl: URL? public let previewUrl: UnicodeURL?
public let meta: Meta? public let meta: Meta?
public let description: String? public let description: String?
public let blurhash: String? public let blurhash: String?

View file

@ -9,7 +9,7 @@ public struct Card: Codable, Hashable {
public static var unknownCase: Self { .unknown } public static var unknownCase: Self { .unknown }
} }
public let url: URL public let url: UnicodeURL
public let title: String public let title: String
public let description: String public let description: String
public let type: CardType public let type: CardType
@ -20,6 +20,6 @@ public struct Card: Codable, Hashable {
public let html: String? public let html: String?
public let width: Int? public let width: Int?
public let height: Int? public let height: Int?
public let image: URL? public let image: UnicodeURL?
public let embedUrl: String? public let embedUrl: String?
} }

View file

@ -4,8 +4,8 @@ import Foundation
public struct Emoji: Codable, Hashable { public struct Emoji: Codable, Hashable {
public let shortcode: String public let shortcode: String
public let staticUrl: String? public let staticUrl: UnicodeURL
public let url: String? public let url: UnicodeURL
public let visibleInPicker: Bool public let visibleInPicker: Bool
public let category: String? public let category: String?
} }

View file

@ -5,11 +5,11 @@ import Foundation
public struct FeaturedTag: Codable, Hashable { public struct FeaturedTag: Codable, Hashable {
public let id: Id public let id: Id
public let name: String public let name: String
public let url: URL public let url: UnicodeURL
public let statusesCount: Int public let statusesCount: Int
public let lastStatusAt: Date public let lastStatusAt: Date
public init(id: FeaturedTag.Id, name: String, url: URL, statusesCount: Int, lastStatusAt: Date) { public init(id: FeaturedTag.Id, name: String, url: UnicodeURL, statusesCount: Int, lastStatusAt: Date) {
self.id = id self.id = id
self.name = name self.name = name
self.url = url self.url = url

View file

@ -5,11 +5,15 @@ import Foundation
public struct IdentityProof: Codable, Hashable { public struct IdentityProof: Codable, Hashable {
public let provider: String public let provider: String
public let providerUsername: String public let providerUsername: String
public let profileUrl: URL public let profileUrl: UnicodeURL
public let proofUrl: URL public let proofUrl: UnicodeURL
public let updatedAt: Date public let updatedAt: Date
public init(provider: String, providerUsername: String, profileUrl: URL, proofUrl: URL, updatedAt: Date) { public init(provider: String,
providerUsername: String,
profileUrl: UnicodeURL,
proofUrl: UnicodeURL,
updatedAt: Date) {
self.provider = provider self.provider = provider
self.providerUsername = providerUsername self.providerUsername = providerUsername
self.profileUrl = profileUrl self.profileUrl = profileUrl

View file

@ -4,7 +4,7 @@ import Foundation
public struct Instance: Codable, Hashable { public struct Instance: Codable, Hashable {
public struct URLs: Codable, Hashable { public struct URLs: Codable, Hashable {
public let streamingApi: URL public let streamingApi: UnicodeURL
} }
public struct Stats: Codable, Hashable { public struct Stats: Codable, Hashable {
@ -25,7 +25,7 @@ public struct Instance: Codable, Hashable {
@DecodableDefault.False public private(set) var invitesEnabled: Bool @DecodableDefault.False public private(set) var invitesEnabled: Bool
public let urls: URLs public let urls: URLs
public let stats: Stats public let stats: Stats
public let thumbnail: URL? public let thumbnail: UnicodeURL?
public let contactAccount: Account? public let contactAccount: Account?
public let maxTootChars: Int? public let maxTootChars: Int?
@ -37,7 +37,7 @@ public struct Instance: Codable, Hashable {
version: String, version: String,
urls: Instance.URLs, urls: Instance.URLs,
stats: Instance.Stats, stats: Instance.Stats,
thumbnail: URL?, thumbnail: UnicodeURL?,
contactAccount: Account?, contactAccount: Account?,
maxTootChars: Int?) { maxTootChars: Int?) {
self.uri = uri self.uri = uri

View file

@ -3,7 +3,7 @@
import Foundation import Foundation
public struct Mention: Codable, Hashable { public struct Mention: Codable, Hashable {
public let url: URL public let url: UnicodeURL
public let username: String public let username: String
public let acct: String public let acct: String
public let id: Account.Id public let id: Account.Id

View file

@ -6,7 +6,7 @@ public struct PushNotification: Codable {
public let accessToken: String public let accessToken: String
public let body: String public let body: String
public let title: String public let title: String
public let icon: URL public let icon: UnicodeURL
public let notificationId: Int public let notificationId: Int
public let notificationType: MastodonNotification.NotificationType public let notificationType: MastodonNotification.NotificationType
public let preferredLocale: String public let preferredLocale: String

View file

@ -13,7 +13,7 @@ public struct PushSubscription: Codable {
@DecodableDefault.True public var status: Bool @DecodableDefault.True public var status: Bool
} }
public let endpoint: URL public let endpoint: UnicodeURL
public let alerts: Alerts public let alerts: Alerts
public let serverKey: String public let serverKey: String
} }

View file

@ -4,7 +4,7 @@ import Foundation
public struct Tag: Codable, Hashable { public struct Tag: Codable, Hashable {
public let name: String public let name: String
public let url: URL public let url: UnicodeURL
public let history: [History]? public let history: [History]?
} }

View file

@ -0,0 +1,37 @@
// Copyright © 2021 Metabolist. All rights reserved.
import Foundation
public struct UnicodeURL: Hashable {
public let raw: String
public let url: URL
}
extension UnicodeURL: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
raw = try container.decode(String.self)
if let url = URL(string: raw) {
self.url = url
} else if let escaped = raw.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) {
let colonUnescaped = escaped.replacingOccurrences(
of: "%3A",
with: ":",
range: escaped.range(of: "%3A"))
guard let url = URL(string: colonUnescaped) else { throw URLError(.badURL) }
self.url = url
} else {
throw URLError(.badURL)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(raw)
}
}

View file

@ -54,7 +54,7 @@ final class NotificationService: UNNotificationServiceExtension {
bestAttemptContent.subtitle = handle bestAttemptContent.subtitle = handle
} }
Self.attachment(imageURL: pushNotification.icon) Self.attachment(imageURL: pushNotification.icon.url)
.map { [$0] } .map { [$0] }
.replaceError(with: []) .replaceError(with: [])
.handleEvents(receiveOutput: { bestAttemptContent.attachments = $0 }) .handleEvents(receiveOutput: { bestAttemptContent.attachments = $0 })

View file

@ -122,7 +122,7 @@ public extension NavigationService {
private extension NavigationService { private extension NavigationService {
func tag(url: URL) -> String? { func tag(url: URL) -> String? {
if status?.tags.first(where: { $0.url.path.lowercased() == url.path.lowercased() }) != nil { if status?.tags.first(where: { $0.url.url.path.lowercased() == url.path.lowercased() }) != nil {
return url.lastPathComponent return url.lastPathComponent
} else if } else if
mastodonAPIClient.instanceURL.host == url.host { mastodonAPIClient.instanceURL.host == url.host {
@ -133,7 +133,9 @@ private extension NavigationService {
} }
func accountId(url: URL) -> String? { func accountId(url: URL) -> String? {
if let mentionId = status?.mentions.first(where: { $0.url.path.lowercased() == url.path.lowercased() })?.id { if let mentionId = status?.mentions.first(where: {
$0.url.url.path.lowercased() == url.path.lowercased()
})?.id {
return mentionId return mentionId
} else if } else if
mastodonAPIClient.instanceURL.host == url.host { mastodonAPIClient.instanceURL.host == url.host {

View file

@ -262,7 +262,7 @@ private extension AddIdentityViewController {
if let instance = instance { if let instance = instance {
self.instanceTitleLabel.text = instance.title self.instanceTitleLabel.text = instance.title
self.instanceURLLabel.text = instance.uri self.instanceURLLabel.text = instance.uri
self.instanceImageView.sd_setImage(with: instance.thumbnail) self.instanceImageView.sd_setImage(with: instance.thumbnail?.url)
self.instanceStackView.isHidden_stackViewSafe = false self.instanceStackView.isHidden_stackViewSafe = false
if instance.registrations { if instance.registrations {

View file

@ -52,9 +52,9 @@ final class EditAttachmentViewController: UIViewController {
let player: AVPlayer let player: AVPlayer
if viewModel.attachment.type == .video { if viewModel.attachment.type == .video {
player = PlayerCache.shared.player(url: viewModel.attachment.url) player = PlayerCache.shared.player(url: viewModel.attachment.url.url)
} else { } else {
player = AVPlayer(url: viewModel.attachment.url) player = AVPlayer(url: viewModel.attachment.url.url)
} }
player.isMuted = false player.isMuted = false
@ -188,7 +188,7 @@ private extension EditAttachmentViewController {
func detectTextFromPicture() { func detectTextFromPicture() {
SDWebImageManager.shared.loadImage( SDWebImageManager.shared.loadImage(
with: viewModel.attachment.url, with: viewModel.attachment.url.url,
options: [], options: [],
progress: nil) { image, _, _, _, _, _ in progress: nil) { image, _, _, _, _, _ in
guard let cgImage = image?.cgImage else { return } guard let cgImage = image?.cgImage else { return }

View file

@ -127,7 +127,7 @@ final class ImageViewController: UIViewController {
playerView.isHidden = true playerView.isHidden = true
let placeholderImage: UIImage? let placeholderImage: UIImage?
let cachedImageKey = viewModel.attachment.previewUrl?.absoluteString let cachedImageKey = viewModel.attachment.previewUrl?.url.absoluteString
let cachedImage = SDImageCache.shared.imageFromCache(forKey: cachedImageKey) let cachedImage = SDImageCache.shared.imageFromCache(forKey: cachedImageKey)
if cachedImage != nil { if cachedImage != nil {
@ -139,7 +139,7 @@ final class ImageViewController: UIViewController {
placeholderImage = nil placeholderImage = nil
} }
imageView.sd_setImage(with: viewModel.attachment.url, imageView.sd_setImage(with: viewModel.attachment.url.url,
placeholderImage: placeholderImage) { _, error, _, _ in placeholderImage: placeholderImage) { _, error, _, _ in
if error != nil { if error != nil {
let alertItem = AlertItem(error: ImageError.unableToLoad) let alertItem = AlertItem(error: ImageError.unableToLoad)
@ -150,7 +150,7 @@ final class ImageViewController: UIViewController {
case .gifv: case .gifv:
playerView.tag = viewModel.tag playerView.tag = viewModel.tag
imageView.isHidden = true imageView.isHidden = true
let player = PlayerCache.shared.player(url: viewModel.attachment.url) let player = PlayerCache.shared.player(url: viewModel.attachment.url.url)
player.isMuted = true player.isMuted = true

View file

@ -589,9 +589,9 @@ private extension TableViewController {
let player: AVPlayer let player: AVPlayer
if attachmentViewModel.attachment.type == .video { if attachmentViewModel.attachment.type == .video {
player = PlayerCache.shared.player(url: attachmentViewModel.attachment.url) player = PlayerCache.shared.player(url: attachmentViewModel.attachment.url.url)
} else { } else {
player = AVPlayer(url: attachmentViewModel.attachment.url) player = AVPlayer(url: attachmentViewModel.attachment.url.url)
} }
playerViewController.delegate = self playerViewController.delegate = self

View file

@ -29,9 +29,9 @@ public extension AccountViewModel {
var headerURL: URL { var headerURL: URL {
if identityContext.appPreferences.animateHeaders { if identityContext.appPreferences.animateHeaders {
return accountService.account.header return accountService.account.header.url
} else { } else {
return accountService.account.headerStatic return accountService.account.headerStatic.url
} }
} }
@ -66,9 +66,9 @@ public extension AccountViewModel {
func avatarURL(profile: Bool = false) -> URL { func avatarURL(profile: Bool = false) -> URL {
if identityContext.appPreferences.animateAvatars == .everywhere if identityContext.appPreferences.animateAvatars == .everywhere
|| (identityContext.appPreferences.animateAvatars == .profiles && profile) { || (identityContext.appPreferences.animateAvatars == .profiles && profile) {
return accountService.account.avatar return accountService.account.avatar.url
} else { } else {
return accountService.account.avatarStatic return accountService.account.avatarStatic.url
} }
} }

View file

@ -12,11 +12,11 @@ public struct CardViewModel {
} }
public extension CardViewModel { public extension CardViewModel {
var url: URL { card.url } var url: URL { card.url.url }
var title: String { card.title } var title: String { card.title }
var description: String { card.description } var description: String { card.description }
var imageURL: URL? { card.image } var imageURL: URL? { card.image?.url }
} }

View file

@ -18,13 +18,13 @@ public extension EmojiViewModel {
var system: Bool { emoji.system } var system: Bool { emoji.system }
var url: String? { var url: URL? {
guard case let .custom(emoji, _) = emoji else { return nil } guard case let .custom(emoji, _) = emoji else { return nil }
if identityContext.appPreferences.animateCustomEmojis { if identityContext.appPreferences.animateCustomEmojis {
return emoji.url return emoji.url.url
} else { } else {
return emoji.staticUrl return emoji.staticUrl.url
} }
} }
} }

View file

@ -83,17 +83,17 @@ public extension StatusViewModel {
var avatarURL: URL { var avatarURL: URL {
if identityContext.appPreferences.animateAvatars == .everywhere { if identityContext.appPreferences.animateAvatars == .everywhere {
return statusService.status.displayStatus.account.avatar return statusService.status.displayStatus.account.avatar.url
} else { } else {
return statusService.status.displayStatus.account.avatarStatic return statusService.status.displayStatus.account.avatarStatic.url
} }
} }
var rebloggerAvatarURL: URL { var rebloggerAvatarURL: URL {
if identityContext.appPreferences.animateAvatars == .everywhere { if identityContext.appPreferences.animateAvatars == .everywhere {
return statusService.status.account.avatar return statusService.status.account.avatar.url
} else { } else {
return statusService.status.account.avatarStatic return statusService.status.account.avatarStatic.url
} }
} }
@ -351,7 +351,7 @@ public extension StatusViewModel {
func attachmentSelected(viewModel: AttachmentViewModel) { func attachmentSelected(viewModel: AttachmentViewModel) {
if viewModel.attachment.type == .unknown, let remoteUrl = viewModel.attachment.remoteUrl { if viewModel.attachment.type == .unknown, let remoteUrl = viewModel.attachment.remoteUrl {
urlSelected(remoteUrl) urlSelected(remoteUrl.url)
} else { } else {
eventsSubject.send(Just(.attachment(viewModel, self)).setFailureType(to: Error.self).eraseToAnyPublisher()) eventsSubject.send(Just(.attachment(viewModel, self)).setFailureType(to: Error.self).eraseToAnyPublisher())
} }

View file

@ -64,7 +64,7 @@ final class AttachmentView: UIView {
extension AttachmentView { extension AttachmentView {
func play() { func play() {
let player = PlayerCache.shared.player(url: viewModel.attachment.url) let player = PlayerCache.shared.player(url: viewModel.attachment.url.url)
playerCancellable = NotificationCenter.default.publisher( playerCancellable = NotificationCenter.default.publisher(
for: .AVPlayerItemDidPlayToEndTime, for: .AVPlayerItemDidPlayToEndTime,
@ -180,7 +180,7 @@ private extension AttachmentView {
} }
imageView.sd_setImage( imageView.sd_setImage(
with: viewModel.attachment.previewUrl, with: viewModel.attachment.previewUrl?.url,
placeholderImage: placeholderImage) { [weak self] _, _, _, _ in placeholderImage: placeholderImage) { [weak self] _, _, _, _ in
self?.layoutSubviews() self?.layoutSubviews()
} }

View file

@ -199,7 +199,7 @@ private extension CompositionView {
? $0.identity.account?.avatar ? $0.identity.account?.avatar
: $0.identity.account?.avatarStatic : $0.identity.account?.avatarStatic
self.avatarImageView.sd_setImage(with: avatarURL) self.avatarImageView.sd_setImage(with: avatarURL?.url)
self.changeIdentityButton.accessibilityLabel = $0.identity.handle self.changeIdentityButton.accessibilityLabel = $0.identity.handle
self.changeIdentityButton.accessibilityHint = self.changeIdentityButton.accessibilityHint =
NSLocalizedString("compose.change-identity-button.accessibility-hint", comment: "") NSLocalizedString("compose.change-identity-button.accessibility-hint", comment: "")

View file

@ -76,16 +76,7 @@ private extension EmojiView {
emojiLabel.isHidden = true emojiLabel.isHidden = true
emojiLabel.text = nil emojiLabel.text = nil
imageView.isHidden = false imageView.isHidden = false
imageView.sd_setImage(with: emojiConfiguration.viewModel.url)
let url: URL?
if let urlString = emojiConfiguration.viewModel.url {
url = URL(stringEscapingPath: urlString)
} else {
url = nil
}
imageView.sd_setImage(with: url)
} }
accessibilityLabel = emojiConfiguration.viewModel.name accessibilityLabel = emojiConfiguration.viewModel.name

View file

@ -73,7 +73,7 @@ private extension AutocompleteItemView {
switch autocompleteItemConfiguration.item { switch autocompleteItemConfiguration.item {
case let .account(account): case let .account(account):
let appPreferences = autocompleteItemConfiguration.identityContext.appPreferences let appPreferences = autocompleteItemConfiguration.identityContext.appPreferences
let avatarURL = appPreferences.animateAvatars == .everywhere ? account.avatar : account.avatarStatic let avatarURL = (appPreferences.animateAvatars == .everywhere ? account.avatar : account.avatarStatic).url
imageView.sd_setImage(with: avatarURL) imageView.sd_setImage(with: avatarURL)
imageView.isHidden = false imageView.isHidden = false

View file

@ -77,7 +77,7 @@ private extension InstanceView {
func applyInstanceConfiguration() { func applyInstanceConfiguration() {
let viewModel = instanceConfiguration.viewModel let viewModel = instanceConfiguration.viewModel
imageView.sd_setImage(with: viewModel.instance.thumbnail) imageView.sd_setImage(with: viewModel.instance.thumbnail?.url)
imageView.autoPlayAnimatedImage = !UIAccessibility.isReduceMotionEnabled imageView.autoPlayAnimatedImage = !UIAccessibility.isReduceMotionEnabled
titleLabel.text = viewModel.instance.title titleLabel.text = viewModel.instance.title

View file

@ -138,7 +138,7 @@ private extension EditThumbnailView {
previewImageView.contentMode = .scaleAspectFill previewImageView.contentMode = .scaleAspectFill
previewImageView.clipsToBounds = true previewImageView.clipsToBounds = true
previewImageView.layer.cornerRadius = .defaultCornerRadius previewImageView.layer.cornerRadius = .defaultCornerRadius
previewImageView.sd_setImage(with: viewModel.attachment.previewUrl) previewImageView.sd_setImage(with: viewModel.attachment.previewUrl?.url)
switch viewModel.attachment.type { switch viewModel.attachment.type {
case .image: case .image:
@ -151,10 +151,10 @@ private extension EditThumbnailView {
placeholderImage = nil placeholderImage = nil
} }
imageView.sd_setImage(with: viewModel.attachment.previewUrl, placeholderImage: placeholderImage) imageView.sd_setImage(with: viewModel.attachment.previewUrl?.url, placeholderImage: placeholderImage)
case .gifv: case .gifv:
imageView.isHidden = true imageView.isHidden = true
let player = PlayerCache.shared.player(url: viewModel.attachment.url) let player = PlayerCache.shared.player(url: viewModel.attachment.url.url)
player.isMuted = true player.isMuted = true

View file

@ -69,7 +69,7 @@ private extension SecondaryNavigationTitleView {
? viewModel.identityContext.identity.account?.avatar ? viewModel.identityContext.identity.account?.avatar
: viewModel.identityContext.identity.account?.avatarStatic : viewModel.identityContext.identity.account?.avatarStatic
avatarImageView.sd_setImage(with: avatarURL) avatarImageView.sd_setImage(with: avatarURL?.url)
if let displayName = viewModel.identityContext.identity.account?.displayName, if let displayName = viewModel.identityContext.identity.account?.displayName,
!displayName.isEmpty { !displayName.isEmpty {