mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
Escape unicode in all URLs from API
This commit is contained in:
parent
0ca2bf9653
commit
9552305a78
37 changed files with 136 additions and 107 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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:)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
37
Mastodon/Sources/Mastodon/Entities/UnicodeURL.swift
Normal file
37
Mastodon/Sources/Mastodon/Entities/UnicodeURL.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 })
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: "")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue