diff --git a/Pachyderm/Model/Attachment.swift b/Pachyderm/Model/Attachment.swift index 216a5df7..2e428258 100644 --- a/Pachyderm/Model/Attachment.swift +++ b/Pachyderm/Model/Attachment.swift @@ -14,7 +14,6 @@ public class Attachment: Codable { public let url: URL public let remoteURL: URL? public let previewURL: URL? - public let textURL: URL? public let meta: Metadata? public let description: String? public let blurHash: String? @@ -33,7 +32,6 @@ public class Attachment: Codable { 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) self.blurHash = try? container.decode(String?.self, forKey: .blurHash) @@ -45,7 +43,6 @@ public class Attachment: Codable { case url case remoteURL = "remote_url" case previewURL = "preview_url" - case textURL = "text_url" case meta case description case blurHash = "blurhash" diff --git a/Pachyderm/Model/Card.swift b/Pachyderm/Model/Card.swift index bdcce268..aea2bcf2 100644 --- a/Pachyderm/Model/Card.swift +++ b/Pachyderm/Model/Card.swift @@ -7,17 +7,18 @@ // import Foundation +import WebURL public class Card: Codable { - public let url: URL + public let url: WebURL public let title: String public let description: String - public let image: URL? + public let image: WebURL? public let kind: Kind public let authorName: String? - public let authorURL: URL? + public let authorURL: WebURL? public let providerName: String? - public let providerURL: URL? + public let providerURL: WebURL? public let html: String? public let width: Int? public let height: Int? @@ -26,15 +27,15 @@ public class Card: Codable { public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.url = try container.decode(URL.self, forKey: .url) + self.url = try container.decode(WebURL.self, forKey: .url) self.title = try container.decode(String.self, forKey: .title) self.description = try container.decode(String.self, forKey: .description) self.kind = try container.decode(Kind.self, forKey: .kind) - self.image = try? container.decodeIfPresent(URL.self, forKey: .image) + self.image = try? container.decodeIfPresent(WebURL.self, forKey: .image) self.authorName = try? container.decodeIfPresent(String.self, forKey: .authorName) - self.authorURL = try? container.decodeIfPresent(URL.self, forKey: .authorURL) + self.authorURL = try? container.decodeIfPresent(WebURL.self, forKey: .authorURL) self.providerName = try? container.decodeIfPresent(String.self, forKey: .providerName) - self.providerURL = try? container.decodeIfPresent(URL.self, forKey: .providerURL) + self.providerURL = try? container.decodeIfPresent(WebURL.self, forKey: .providerURL) self.html = try? container.decodeIfPresent(String.self, forKey: .html) self.width = try? container.decodeIfPresent(Int.self, forKey: .width) self.height = try? container.decodeIfPresent(Int.self, forKey: .height) diff --git a/Pachyderm/Model/Emoji.swift b/Pachyderm/Model/Emoji.swift index 711ce2be..cd6657e6 100644 --- a/Pachyderm/Model/Emoji.swift +++ b/Pachyderm/Model/Emoji.swift @@ -7,29 +7,22 @@ // import Foundation +import WebURL public class Emoji: Codable { public let shortcode: String - public let url: URL - public let staticURL: URL + // these shouldn't need to be WebURLs as they're not external resources, + // but some instances (pleroma?) has emoji urls that Foundation considers malformed so we use WebURL to be more lenient + public let url: WebURL + public let staticURL: WebURL public let visibleInPicker: Bool public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.shortcode = try container.decode(String.self, forKey: .shortcode) - if let url = try? container.decode(URL.self, forKey: .url) { - self.url = url - } else { - let str = try container.decode(String.self, forKey: .url) - self.url = URL(string: str.replacingOccurrences(of: " ", with: "%20"))! - } - if let url = try? container.decode(URL.self, forKey: .staticURL) { - self.staticURL = url - } else { - let staticStr = try container.decode(String.self, forKey: .staticURL) - self.staticURL = URL(string: staticStr.replacingOccurrences(of: " ", with: "%20"))! - } + self.url = try container.decode(WebURL.self, forKey: .url) + self.staticURL = try container.decode(WebURL.self, forKey: .staticURL) self.visibleInPicker = try container.decode(Bool.self, forKey: .visibleInPicker) } diff --git a/Pachyderm/Model/Mention.swift b/Pachyderm/Model/Mention.swift index b8898d61..601e386c 100644 --- a/Pachyderm/Model/Mention.swift +++ b/Pachyderm/Model/Mention.swift @@ -7,9 +7,10 @@ // import Foundation +import WebURL public class Mention: Codable { - public let url: URL + public let url: WebURL public let username: String public let acct: String /// The instance-local ID of the user being mentioned. diff --git a/Pachyderm/Model/Status.swift b/Pachyderm/Model/Status.swift index e8b817e8..434e27ae 100644 --- a/Pachyderm/Model/Status.swift +++ b/Pachyderm/Model/Status.swift @@ -38,8 +38,7 @@ public final class Status: /*StatusProtocol,*/ Decodable { public let bookmarked: Bool? public let card: Card? public let poll: Poll? - // Hometown only - // TODO: glitch too? + // Hometown, Glitch only public let localOnly: Bool? public var applicationName: String? { application?.name } diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 64392e6a..e7ef0f70 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -126,6 +126,7 @@ D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; }; D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; }; D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; }; + D635ED5027ACD9260003635B /* WebURL in Frameworks */ = {isa = PBXBuildFile; productRef = D635ED4F27ACD9260003635B /* WebURL */; }; D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; }; D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; }; D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63A8D0A2561C27F00D9DFFF /* ProfileStatusesViewController.swift */; }; @@ -177,6 +178,8 @@ D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; }; D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */; }; D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; }; + D6676CA327A8D0020052936B /* WebURL in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA227A8D0020052936B /* WebURL */; }; + D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA427A8D0020052936B /* WebURLFoundationExtras */; }; D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */; }; D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */; }; D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F42135BCD50057A976 /* ConversationTableViewController.swift */; }; @@ -766,6 +769,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D635ED5027ACD9260003635B /* WebURL in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -781,10 +785,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D6676CA327A8D0020052936B /* WebURL in Frameworks */, D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */, D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */, D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */, D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */, + D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1720,6 +1726,9 @@ dependencies = ( ); name = Pachyderm; + packageProductDependencies = ( + D635ED4F27ACD9260003635B /* WebURL */, + ); productName = Pachyderm; productReference = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; productType = "com.apple.product-type.framework"; @@ -1765,6 +1774,8 @@ D6B0539E23BD2BA300A066FA /* SheetController */, D69CCBBE249E6EFD000AF167 /* CrashReporter */, D60CFFDA24A290BA00D00083 /* SwiftSoup */, + D6676CA227A8D0020052936B /* WebURL */, + D6676CA427A8D0020052936B /* WebURLFoundationExtras */, ); productName = Tusker; productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */; @@ -1882,6 +1893,7 @@ D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */, D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */, D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */, + D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */, ); productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */; projectDirPath = ""; @@ -2892,6 +2904,14 @@ minimumVersion = 2.3.2; }; }; + D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/karwa/swift-url"; + requirement = { + branch = main; + kind = branch; + }; + }; D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/microsoft/plcrashreporter"; @@ -2916,6 +2936,21 @@ package = D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; + D635ED4F27ACD9260003635B /* WebURL */ = { + isa = XCSwiftPackageProductDependency; + package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */; + productName = WebURL; + }; + D6676CA227A8D0020052936B /* WebURL */ = { + isa = XCSwiftPackageProductDependency; + package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */; + productName = WebURL; + }; + D6676CA427A8D0020052936B /* WebURLFoundationExtras */ = { + isa = XCSwiftPackageProductDependency; + package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */; + productName = WebURLFoundationExtras; + }; D69CCBBE249E6EFD000AF167 /* CrashReporter */ = { isa = XCSwiftPackageProductDependency; package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */; diff --git a/Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved index abb274a3..db44909b 100644 --- a/Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Tusker.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -2,7 +2,7 @@ "object": { "pins": [ { - "package": "plcrashreporter", + "package": "PLCrashReporter", "repositoryURL": "https://github.com/microsoft/plcrashreporter", "state": { "branch": null, @@ -19,6 +19,24 @@ "version": null } }, + { + "package": "swift-system", + "repositoryURL": "https://github.com/apple/swift-system.git", + "state": { + "branch": null, + "revision": "836bc4557b74fe6d2660218d56e3ce96aff76574", + "version": "1.1.1" + } + }, + { + "package": "swift-url", + "repositoryURL": "https://github.com/karwa/swift-url", + "state": { + "branch": "main", + "revision": "1519936d9813af86e57c06dc7f727cb281de9f16", + "version": null + } + }, { "package": "SwiftSoup", "repositoryURL": "https://github.com/scinfu/SwiftSoup.git", diff --git a/Tusker/Screens/Compose/EmojiCollectionViewCell.swift b/Tusker/Screens/Compose/EmojiCollectionViewCell.swift index d23fc531..fbce6b93 100644 --- a/Tusker/Screens/Compose/EmojiCollectionViewCell.swift +++ b/Tusker/Screens/Compose/EmojiCollectionViewCell.swift @@ -8,6 +8,7 @@ import UIKit import Pachyderm +import WebURLFoundationExtras class EmojiCollectionViewCell: UICollectionViewCell { @@ -45,7 +46,7 @@ class EmojiCollectionViewCell: UICollectionViewCell { func updateUI(emoji: Emoji) { currentEmojiShortcode = emoji.shortcode - imageRequest = ImageCache.emojis.get(emoji.url) { [weak self] (_, image) in + imageRequest = ImageCache.emojis.get(URL(emoji.url)!) { [weak self] (_, image) in guard let image = image else { return } DispatchQueue.main.async { [weak self] in guard let self = self, self.currentEmojiShortcode == emoji.shortcode else { return } diff --git a/Tusker/Views/AccountDisplayNameLabel.swift b/Tusker/Views/AccountDisplayNameLabel.swift index 2292be56..452320fe 100644 --- a/Tusker/Views/AccountDisplayNameLabel.swift +++ b/Tusker/Views/AccountDisplayNameLabel.swift @@ -8,6 +8,7 @@ import SwiftUI import Pachyderm +import WebURLFoundationExtras private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) @@ -48,7 +49,7 @@ struct AccountDisplayNameLabel: View { } group.enter() - let request = ImageCache.emojis.get(emoji.url) { (_, image) in + let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in defer { group.leave() } guard let image = image else { return } diff --git a/Tusker/Views/BaseEmojiLabel.swift b/Tusker/Views/BaseEmojiLabel.swift index b0f6adb1..7e53818a 100644 --- a/Tusker/Views/BaseEmojiLabel.swift +++ b/Tusker/Views/BaseEmojiLabel.swift @@ -8,6 +8,7 @@ import UIKit import Pachyderm +import WebURLFoundationExtras private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) @@ -56,7 +57,7 @@ extension BaseEmojiLabel { foundEmojis = true - if let image = ImageCache.emojis.get(emoji.url)?.image { + if let image = ImageCache.emojis.get(URL(emoji.url)!)?.image { // if the image is cached, add it immediately emojiImages[emoji.shortcode] = image } else { @@ -64,9 +65,9 @@ extension BaseEmojiLabel { group.enter() // todo: ImageCache.emojis.get here will re-check the memory and disk caches, there should be another method to force-refetch - let request = ImageCache.emojis.get(emoji.url) { (_, image) in + let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in guard let image = image, - let transformedImage = ImageGrayscalifier.convertIfNecessary(url: emoji.url, image: image) else { + let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: image) else { group.leave() return } diff --git a/Tusker/Views/CustomEmojiImageView.swift b/Tusker/Views/CustomEmojiImageView.swift index 30965912..f046b9b8 100644 --- a/Tusker/Views/CustomEmojiImageView.swift +++ b/Tusker/Views/CustomEmojiImageView.swift @@ -8,6 +8,7 @@ import SwiftUI import Pachyderm +import WebURLFoundationExtras struct CustomEmojiImageView: View { let emoji: Emoji @@ -33,7 +34,7 @@ struct CustomEmojiImageView: View { } private func loadImage() { - request = ImageCache.emojis.get(emoji.url) { (_, image) in + request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in DispatchQueue.main.async { self.request = nil if let image = image { diff --git a/Tusker/Views/Status/StatusCardView.swift b/Tusker/Views/Status/StatusCardView.swift index 5b6005cc..d97f722c 100644 --- a/Tusker/Views/Status/StatusCardView.swift +++ b/Tusker/Views/Status/StatusCardView.swift @@ -9,6 +9,7 @@ import UIKit import Pachyderm import SafariServices +import WebURLFoundationExtras class StatusCardView: UIView { @@ -141,9 +142,9 @@ class StatusCardView: UIView { if let imageURL = card.image { placeholderImageView.isHidden = true - imageRequest = ImageCache.attachments.get(imageURL, completion: { (_, image) in + imageRequest = ImageCache.attachments.get(URL(imageURL)!, completion: { (_, image) in guard let image = image, - let transformedImage = ImageGrayscalifier.convertIfNecessary(url: imageURL, image: image) else { + let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(imageURL)!, image: image) else { return } DispatchQueue.main.async { @@ -200,7 +201,7 @@ class StatusCardView: UIView { setNeedsDisplay() if let card = card, let delegate = navigationDelegate { - delegate.selected(url: card.url) + delegate.selected(url: URL(card.url)!) } } @@ -219,9 +220,9 @@ extension StatusCardView: UIContextMenuInteractionDelegate { guard let card = card else { return nil } return UIContextMenuConfiguration(identifier: nil) { - return SFSafariViewController(url: card.url) + return SFSafariViewController(url: URL(card.url)!) } actionProvider: { (_) in - let actions = self.actionsForURL(card.url, sourceView: self) + let actions = self.actionsForURL(URL(card.url)!, sourceView: self) return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions) } } diff --git a/Tusker/Views/StatusContentTextView.swift b/Tusker/Views/StatusContentTextView.swift index 6ebf00ac..f8fd23b6 100644 --- a/Tusker/Views/StatusContentTextView.swift +++ b/Tusker/Views/StatusContentTextView.swift @@ -27,7 +27,7 @@ class StatusContentTextView: ContentTextView { let status = mastodonController.persistentContainer.status(for: statusID) { mention = status.mentions.first { (mention) in // Mastodon and Pleroma include the @ in the text, GNU Social does not - (text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host + (text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host!.serialized } } else { mention = nil