From adaf8dc217f0857bef9b8dcc7fae0efc3c08bc3d Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 15 Dec 2024 14:20:48 -0500 Subject: [PATCH] Replace WebURL with URL.ParseStrategy Closes #170 --- .../NotificationService.swift | 16 +--- Packages/Pachyderm/Package.swift | 4 +- .../Pachyderm/Sources/Pachyderm/Client.swift | 5 +- .../Pachyderm/Model/Announcement.swift | 5 +- .../Sources/Pachyderm/Model/Card.swift | 25 +++--- .../Sources/Pachyderm/Model/Emoji.swift | 16 +--- .../Sources/Pachyderm/Model/Hashtag.swift | 8 +- .../Sources/Pachyderm/Model/Mention.swift | 12 +-- .../Pachyderm/Model/Notification.swift | 5 +- .../Pachyderm/Model/PushNotification.swift | 5 +- .../Sources/Pachyderm/Model/Status.swift | 10 +-- .../Utilities/NotificationGroup.swift | 3 +- .../Pachyderm/Utilities/URLDecoder.swift | 82 +++++++++++++++++++ .../Tests/PachydermTests/URLTests.swift | 21 ++--- ShareExtension/ShareHostingController.swift | 3 +- Tusker.xcodeproj/project.pbxproj | 25 ------ Tusker/CoreData/FollowedHashtag.swift | 3 +- Tusker/CoreData/SavedHashtag.swift | 3 +- Tusker/CoreData/StatusMO.swift | 3 +- Tusker/HTMLConverter.swift | 15 +--- .../AnnouncementContentTextView.swift | 5 +- .../Announcements/AnnouncementListRow.swift | 5 +- .../ConversationViewController.swift | 6 +- .../Explore/ExploreViewController.swift | 6 +- .../TrendingHashtagsViewController.swift | 6 +- .../TrendingLinkCardCollectionViewCell.swift | 3 +- .../Explore/TrendingLinkCardView.swift | 1 - .../Explore/TrendingLinksViewController.swift | 14 ++-- .../Explore/TrendsViewController.swift | 19 +---- .../FastSwitchingAccountView.swift | 7 +- ...nNotificationGroupCollectionViewCell.swift | 2 +- ...otificationsCollectionViewController.swift | 2 +- .../Appearance/MockStatusView.swift | 5 +- .../Preferences/PrefsAccountView.swift | 8 +- .../Search/SearchResultsViewController.swift | 3 +- ...tatusActionAccountListViewController.swift | 3 +- Tusker/Screens/Utilities/Previewing.swift | 17 +--- Tusker/Views/AccountDisplayNameView.swift | 3 +- Tusker/Views/BaseEmojiLabel.swift | 7 +- Tusker/Views/ContentTextView.swift | 2 - Tusker/Views/CustomEmojiImageView.swift | 3 +- Tusker/Views/Status/StatusCardView.swift | 24 +++--- Tusker/Views/StatusContentTextView.swift | 5 +- 43 files changed, 187 insertions(+), 238 deletions(-) create mode 100644 Packages/Pachyderm/Sources/Pachyderm/Utilities/URLDecoder.swift diff --git a/NotificationExtension/NotificationService.swift b/NotificationExtension/NotificationService.swift index a2754ee4b..9e00a67de 100644 --- a/NotificationExtension/NotificationService.swift +++ b/NotificationExtension/NotificationService.swift @@ -14,7 +14,6 @@ import OSLog import Pachyderm import Intents import HTMLStreamer -import WebURL import UIKit import TuskerPreferences @@ -238,8 +237,7 @@ class NotificationService: UNNotificationServiceExtension { for match in emojiRegex.matches(in: content.body, range: NSRange(location: 0, length: content.body.utf16.count)).reversed() { let emojiName = (content.body as NSString).substring(with: match.range(at: 1)) guard let emoji = notification.status?.emojis.first(where: { $0.shortcode == emojiName }), - let url = URL(emoji.url), - let (data, _) = try? await URLSession.shared.data(from: url), + let (data, _) = try? await URLSession.shared.data(from: emoji.url), let image = UIImage(data: data) else { continue } @@ -368,17 +366,7 @@ private func decodeBase64URL(_ s: String) -> Data? { // copied from HTMLConverter.Callbacks, blergh private struct HTMLCallbacks: HTMLConversionCallbacks { static func makeURL(string: String) -> URL? { - // Converting WebURL to URL is a small but non-trivial expense (since it works by - // serializing the WebURL as a string and then having Foundation parse it again), - // so, if available, use the system parser which doesn't require another round trip. - if let url = try? URL.ParseStrategy().parse(string) { - url - } else if let web = WebURL(string), - let url = URL(web) { - url - } else { - nil - } + try? URL.ParseStrategy().parse(string) } static func elementAction(name: String, attributes: [Attribute]) -> ElementAction { diff --git a/Packages/Pachyderm/Package.swift b/Packages/Pachyderm/Package.swift index a1be7f1d9..959bd7b1a 100644 --- a/Packages/Pachyderm/Package.swift +++ b/Packages/Pachyderm/Package.swift @@ -7,6 +7,7 @@ let package = Package( name: "Pachyderm", platforms: [ .iOS(.v16), + .macOS(.v13), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -16,7 +17,6 @@ let package = Package( ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(url: "https://github.com/karwa/swift-url.git", exact: "0.4.2"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -24,8 +24,6 @@ let package = Package( .target( name: "Pachyderm", dependencies: [ - .product(name: "WebURL", package: "swift-url"), - .product(name: "WebURLFoundationExtras", package: "swift-url"), ], swiftSettings: [ .swiftLanguageMode(.v5) diff --git a/Packages/Pachyderm/Sources/Pachyderm/Client.swift b/Packages/Pachyderm/Sources/Pachyderm/Client.swift index 6065e8419..f5f157e58 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Client.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Client.swift @@ -7,7 +7,6 @@ // import Foundation -import WebURL /** The base Mastodon API client. @@ -202,8 +201,8 @@ public struct Client: Sendable { let wellKnown = Request(method: .get, path: "/.well-known/nodeinfo") let wellKnownResults = try await run(wellKnown).0 if let url = wellKnownResults.links.first(where: { $0.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" }), - let href = WebURL(url.href), - href.host == WebURL(self.baseURL)?.host { + let href = try? URL.ParseStrategy().parse(url.href), + href.host == self.baseURL.host() { let nodeInfo = Request(method: .get, path: Endpoint(stringLiteral: href.path)) return try await run(nodeInfo).0 } else { diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/Announcement.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Announcement.swift index b4735994d..1d7c61f13 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Announcement.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Announcement.swift @@ -6,7 +6,6 @@ // import Foundation -import WebURL public struct Announcement: Decodable, Sendable, Hashable, Identifiable { public let id: String @@ -60,7 +59,7 @@ extension Announcement { public struct Account: Decodable, Sendable, Hashable { public let id: String public let username: String - public let url: WebURL + @URLDecoder public var url: URL public let acct: String } } @@ -68,7 +67,7 @@ extension Announcement { extension Announcement { public struct Status: Decodable, Sendable, Hashable { public let id: String - public let url: WebURL + @URLDecoder public var url: URL } } diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/Card.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Card.swift index f3dbd14e6..f60756a2f 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Card.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Card.swift @@ -7,18 +7,17 @@ // import Foundation -import WebURL public struct Card: Codable, Sendable { - public let url: WebURL + @URLDecoder public var url: URL public let title: String public let description: String - public let image: WebURL? + @OptionalURLDecoder public var image: URL? public let kind: Kind public let authorName: String? - public let authorURL: WebURL? + @OptionalURLDecoder public var authorURL: URL? public let providerName: String? - public let providerURL: WebURL? + @OptionalURLDecoder public var providerURL: URL? public let html: String? public let width: Int? public let height: Int? @@ -27,15 +26,15 @@ public struct Card: Codable, Sendable { public let history: [History]? public init( - url: WebURL, + url: URL, title: String, description: String, - image: WebURL? = nil, + image: URL? = nil, kind: Card.Kind, authorName: String? = nil, - authorURL: WebURL? = nil, + authorURL: URL? = nil, providerName: String? = nil, - providerURL: WebURL? = nil, + providerURL: URL? = nil, html: String? = nil, width: Int? = nil, height: Int? = nil, @@ -61,15 +60,15 @@ public struct Card: Codable, Sendable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.url = try container.decode(WebURL.self, forKey: .url) + self._url = try container.decode(URLDecoder.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(WebURL.self, forKey: .image) + self._image = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .image) ?? nil self.authorName = try? container.decodeIfPresent(String.self, forKey: .authorName) - self.authorURL = try? container.decodeIfPresent(WebURL.self, forKey: .authorURL) + self._authorURL = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .authorURL) ?? nil self.providerName = try? container.decodeIfPresent(String.self, forKey: .providerName) - self.providerURL = try? container.decodeIfPresent(WebURL.self, forKey: .providerURL) + self._providerURL = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .providerURL) ?? nil 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/Packages/Pachyderm/Sources/Pachyderm/Model/Emoji.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Emoji.swift index 8a11f139d..1d08e99c9 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Emoji.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Emoji.swift @@ -7,14 +7,11 @@ // import Foundation -import WebURL public struct Emoji: Codable, Sendable { public let shortcode: String - // 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 + @URLDecoder public var url: URL + @URLDecoder public var staticURL: URL public let visibleInPicker: Bool public let category: String? @@ -22,13 +19,8 @@ public struct Emoji: Codable, Sendable { let container = try decoder.container(keyedBy: CodingKeys.self) self.shortcode = try container.decode(String.self, forKey: .shortcode) - do { - self.url = try container.decode(WebURL.self, forKey: .url) - } catch { - let s = try? container.decode(String.self, forKey: .url) - throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath + [CodingKeys.url], debugDescription: "Could not decode URL '\(s ?? "")'", underlyingError: error)) - } - self.staticURL = try container.decode(WebURL.self, forKey: .staticURL) + self._url = try container.decode(URLDecoder.self, forKey: .url) + self._staticURL = try container.decode(URLDecoder.self, forKey: .staticURL) self.visibleInPicker = try container.decode(Bool.self, forKey: .visibleInPicker) self.category = try container.decodeIfPresent(String.self, forKey: .category) } diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/Hashtag.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Hashtag.swift index 85b2c8775..3d86209af 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Hashtag.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Hashtag.swift @@ -7,12 +7,10 @@ // import Foundation -import WebURL -import WebURLFoundationExtras public struct Hashtag: Codable, Sendable { public let name: String - public let url: WebURL + @URLDecoder public var url: URL /// Only present when returned from the trending hashtags endpoint public let history: [History]? /// Only present on Mastodon >= 4 and when logged in @@ -20,7 +18,7 @@ public struct Hashtag: Codable, Sendable { public init(name: String, url: URL) { self.name = name - self.url = WebURL(url)! + self.url = url self.history = nil self.following = nil } @@ -29,7 +27,7 @@ public struct Hashtag: Codable, Sendable { let container = try decoder.container(keyedBy: CodingKeys.self) self.name = try container.decode(String.self, forKey: .name) // pixelfed (possibly others) don't fully escape special characters in the hashtag url - self.url = try container.decode(WebURL.self, forKey: .url) + self._url = try container.decode(URLDecoder.self, forKey: .url) self.history = try container.decodeIfPresent([History].self, forKey: .history) self.following = try container.decodeIfPresent(Bool.self, forKey: .following) } diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/Mention.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Mention.swift index f92d3c462..d48e13c73 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Mention.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Mention.swift @@ -7,10 +7,9 @@ // import Foundation -import WebURL public struct Mention: Codable, Sendable { - public let url: WebURL + @URLDecoder public var url: URL public let username: String public let acct: String /// The instance-local ID of the user being mentioned. @@ -21,15 +20,10 @@ public struct Mention: Codable, Sendable { self.username = try container.decode(String.self, forKey: .username) self.acct = try container.decode(String.self, forKey: .acct) self.id = try container.decode(String.self, forKey: .id) - do { - self.url = try container.decode(WebURL.self, forKey: .url) - } catch { - let s = try? container.decode(String.self, forKey: .url) - throw DecodingError.dataCorruptedError(forKey: .url, in: container, debugDescription: "Could not decode URL '\(s ?? "")'") - } + self._url = try container.decode(URLDecoder.self, forKey: .url) } - public init(url: WebURL, username: String, acct: String, id: String) { + public init(url: URL, username: String, acct: String, id: String) { self.url = url self.username = username self.acct = acct diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/Notification.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Notification.swift index 2053b4a45..303e37bb4 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Notification.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Notification.swift @@ -7,7 +7,6 @@ // import Foundation -import WebURL public struct Notification: Decodable, Sendable { public let id: String @@ -18,7 +17,7 @@ public struct Notification: Decodable, Sendable { // Only present for pleroma emoji reactions // Either an emoji or :shortcode: (for akkoma custom emoji reactions) public let emoji: String? - public let emojiURL: WebURL? + @OptionalURLDecoder public var emojiURL: URL? public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -33,7 +32,7 @@ public struct Notification: Decodable, Sendable { self.account = try container.decode(Account.self, forKey: .account) self.status = try container.decodeIfPresent(Status.self, forKey: .status) self.emoji = try container.decodeIfPresent(String.self, forKey: .emoji) - self.emojiURL = try container.decodeIfPresent(WebURL.self, forKey: .emojiURL) + self._emojiURL = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .emojiURL) ?? nil } public static func dismiss(id notificationID: String) -> Request { diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/PushNotification.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/PushNotification.swift index 178cab03a..a8456e044 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/PushNotification.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/PushNotification.swift @@ -6,14 +6,13 @@ // import Foundation -import WebURL public struct PushNotification: Decodable { public let accessToken: String public let preferredLocale: String public let notificationID: String public let notificationType: Notification.Kind - public let icon: WebURL + @URLDecoder public var icon: URL public let title: String public let body: String @@ -29,7 +28,7 @@ public struct PushNotification: Decodable { self.notificationID = i.description } self.notificationType = try container.decode(Notification.Kind.self, forKey: .notificationType) - self.icon = try container.decode(WebURL.self, forKey: .icon) + self._icon = try container.decode(URLDecoder.self, forKey: .icon) self.title = try container.decode(String.self, forKey: .title) self.body = try container.decode(String.self, forKey: .body) } diff --git a/Packages/Pachyderm/Sources/Pachyderm/Model/Status.swift b/Packages/Pachyderm/Sources/Pachyderm/Model/Status.swift index 33e26e3df..0f5f1b824 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Model/Status.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Model/Status.swift @@ -7,7 +7,6 @@ // import Foundation -import WebURL public final class Status: StatusProtocol, Decodable, Sendable { /// The pseudo-visibility used by instance types (Akkoma) that overload the visibility for local-only posts. @@ -15,7 +14,8 @@ public final class Status: StatusProtocol, Decodable, Sendable { public let id: String public let uri: String - public let url: WebURL? + private let _url: OptionalURLDecoder + public var url: URL? { _url.wrappedValue } public let account: Account public let inReplyToID: String? public let inReplyToAccountID: String? @@ -55,13 +55,13 @@ public final class Status: StatusProtocol, Decodable, Sendable { self.id = try container.decode(String.self, forKey: .id) self.uri = try container.decode(String.self, forKey: .uri) do { - self.url = try container.decodeIfPresent(WebURL.self, forKey: .url) + self._url = try container.decodeIfPresent(OptionalURLDecoder.self, forKey: .url) ?? nil } catch { let s = try? container.decode(String.self, forKey: .url) if s == "" { - self.url = nil + self._url = OptionalURLDecoder(wrappedValue: nil) } else { - throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath + [CodingKeys.url], debugDescription: "Could not decode URL '\(s ?? "")'", underlyingError: error)) + throw error } } self.account = try container.decode(Account.self, forKey: .account) diff --git a/Packages/Pachyderm/Sources/Pachyderm/Utilities/NotificationGroup.swift b/Packages/Pachyderm/Sources/Pachyderm/Utilities/NotificationGroup.swift index 47a9e5b15..38a4d34e2 100644 --- a/Packages/Pachyderm/Sources/Pachyderm/Utilities/NotificationGroup.swift +++ b/Packages/Pachyderm/Sources/Pachyderm/Utilities/NotificationGroup.swift @@ -7,7 +7,6 @@ // import Foundation -import WebURL public struct NotificationGroup: Identifiable, Hashable, Sendable { public private(set) var notifications: [Notification] @@ -150,7 +149,7 @@ public struct NotificationGroup: Identifiable, Hashable, Sendable { case poll case update case status - case emojiReaction(String, WebURL?) + case emojiReaction(String, URL?) case unknown var notificationKind: Notification.Kind { diff --git a/Packages/Pachyderm/Sources/Pachyderm/Utilities/URLDecoder.swift b/Packages/Pachyderm/Sources/Pachyderm/Utilities/URLDecoder.swift new file mode 100644 index 000000000..502595fde --- /dev/null +++ b/Packages/Pachyderm/Sources/Pachyderm/Utilities/URLDecoder.swift @@ -0,0 +1,82 @@ +// +// URLDecoder.swift +// Pachyderm +// +// Created by Shadowfacts on 12/15/24. +// +import Foundation + +private let parseStrategy = URL.ParseStrategy() + .scheme(.required) + .user(.optional) + .password(.optional) + .host(.required) + .port(.optional) + .path(.optional) + .query(.optional) + .fragment(.optional) + +private let formatStyle = URL.FormatStyle() + .scheme(.always) + .user(.omitWhen(.user, matches: [""])) + .password(.omitWhen(.password, matches: [""])) + .host(.always) + .port(.omitIfHTTPFamily) + .path(.always) + .query(.omitWhen(.query, matches: [""])) + .fragment(.omitWhen(.fragment, matches: [""])) + +@propertyWrapper +public struct URLDecoder: Codable, Sendable, Hashable { + public var wrappedValue: URL + + public init(wrappedValue: URL) { + self.wrappedValue = wrappedValue + } + + public init(from decoder: any Decoder) throws { + let s = try decoder.singleValueContainer().decode(String.self) + self.wrappedValue = try parseStrategy.parse(s) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(wrappedValue.formatted(formatStyle)) + } +} + +@propertyWrapper +public struct OptionalURLDecoder: Codable, Sendable, Hashable, ExpressibleByNilLiteral { + public var wrappedValue: URL? + + public init(wrappedValue: URL?) { + self.wrappedValue = wrappedValue + } + + public init(nilLiteral: ()) { + self.wrappedValue = nil + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self.wrappedValue = nil + } else { + let s = try container.decode(String.self) + do { + self.wrappedValue = try parseStrategy.parse(s) + } catch { + throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Could not decode URL '\(s)'", underlyingError: error)) + } + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + if let wrappedValue { + try container.encode(wrappedValue.formatted(formatStyle)) + } else { + try container.encodeNil() + } + } +} diff --git a/Packages/Pachyderm/Tests/PachydermTests/URLTests.swift b/Packages/Pachyderm/Tests/PachydermTests/URLTests.swift index 7564c398b..328060dcb 100644 --- a/Packages/Pachyderm/Tests/PachydermTests/URLTests.swift +++ b/Packages/Pachyderm/Tests/PachydermTests/URLTests.swift @@ -6,20 +6,21 @@ // import XCTest -import WebURL -import WebURLFoundationExtras +@testable import Pachyderm class URLTests: XCTestCase { func testDecodeURL() { - XCTAssertNotNil(WebURL(URL(string: "https://xn--baw-joa.social/@unituebingen")!)) - XCTAssertNotNil(WebURL("https://xn--baw-joa.social/@unituebingen")) - XCTAssertNotNil(URLComponents(string: "https://xn--baw-joa.social/test/é")) - XCTAssertNotNil(WebURL("https://xn--baw-joa.social/test/é")) - if #available(iOS 16.0, *) { - XCTAssertNotNil(try? URL.ParseStrategy().parse("https://xn--baw-joa.social/test/é")) - XCTAssertNotNil(try? URL.ParseStrategy().parse("http://見.香港/热狗/🌭")) - } + XCTAssertNotNil(try? URL.ParseStrategy().parse("https://xn--baw-joa.social/test/é")) + XCTAssertNotNil(try? URL.ParseStrategy().parse("http://見.香港/热狗/🌭")) + } + + func testRoundtripURL() throws { + let orig = URLDecoder(wrappedValue: URL(string: "https://example.com")!) + let encoded = try JSONEncoder().encode(orig) + print(String(data: encoded, encoding: .utf8)!) + let decoded = try JSONDecoder().decode(URLDecoder.self, from: encoded) + XCTAssertEqual(orig.wrappedValue, decoded.wrappedValue) } } diff --git a/ShareExtension/ShareHostingController.swift b/ShareExtension/ShareHostingController.swift index dff8f3d71..4f42c5f69 100644 --- a/ShareExtension/ShareHostingController.swift +++ b/ShareExtension/ShareHostingController.swift @@ -9,7 +9,6 @@ import SwiftUI import ComposeUI import TuskerComponents -import WebURLFoundationExtras import Combine import TuskerPreferences @@ -46,7 +45,7 @@ class ShareHostingController: UIHostingController { currentAccountContainerView: { AnyView(SwitchAccountContainerView(content: $0, mastodonContextPublisher: mastodonContextPublisher)) }, replyContentView: { _, _ in fatalError("replies aren't allowed in share sheet") }, emojiImageView: { - AnyView(AsyncImage(url: URL($0.url)!) { + AnyView(AsyncImage(url: $0.url) { $0 .resizable() .aspectRatio(contentMode: .fit) diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 3e451d7c4..5208f2243 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -103,7 +103,6 @@ D630C3E12BC61C6700208903 /* UserAccounts in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3E02BC61C6700208903 /* UserAccounts */; }; D630C3E52BC6313400208903 /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3E42BC6313400208903 /* Pachyderm */; }; D630C4232BC7842C00208903 /* HTMLStreamer in Frameworks */ = {isa = PBXBuildFile; productRef = D630C4222BC7842C00208903 /* HTMLStreamer */; }; - D630C4252BC7845800208903 /* WebURL in Frameworks */ = {isa = PBXBuildFile; productRef = D630C4242BC7845800208903 /* WebURL */; }; D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; }; 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 */; }; @@ -164,7 +163,6 @@ D6620ACE2511A0ED00312CA0 /* StatusStateResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */; }; D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; }; D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; }; - D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA427A8D0020052936B /* WebURLFoundationExtras */; }; D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; }; D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; }; D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D674A50827F9128D00BA03AC /* Pachyderm */; }; @@ -818,7 +816,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D630C4252BC7845800208903 /* WebURL in Frameworks */, D62220472C7EA8DF003E43B7 /* TuskerPreferences in Frameworks */, D630C4232BC7842C00208903 /* HTMLStreamer in Frameworks */, D630C3E52BC6313400208903 /* Pachyderm in Frameworks */, @@ -855,7 +852,6 @@ D6934F2C2BA7AD32002B1C8D /* GalleryVC in Frameworks */, D6552367289870790048A653 /* ScreenCorners in Frameworks */, D60BB3942B30076F00DAEA65 /* HTMLStreamer in Frameworks */, - D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */, D63CC702290EC0B8000E19DE /* Sentry in Frameworks */, D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */, ); @@ -1781,7 +1777,6 @@ D630C3E02BC61C6700208903 /* UserAccounts */, D630C3E42BC6313400208903 /* Pachyderm */, D630C4222BC7842C00208903 /* HTMLStreamer */, - D630C4242BC7845800208903 /* WebURL */, D62220462C7EA8DF003E43B7 /* TuskerPreferences */, ); productName = NotificationExtension; @@ -1833,7 +1828,6 @@ ); name = Tusker; packageProductDependencies = ( - D6676CA427A8D0020052936B /* WebURLFoundationExtras */, D674A50827F9128D00BA03AC /* Pachyderm */, D6552366289870790048A653 /* ScreenCorners */, D63CC701290EC0B8000E19DE /* Sentry */, @@ -1960,7 +1954,6 @@ ); mainGroup = D6D4DDC3212518A000E1C4BB; packageReferences = ( - D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */, D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */, D63CC700290EC0B8000E19DE /* XCRemoteSwiftPackageReference "sentry-cocoa" */, D60BB3922B30076F00DAEA65 /* XCRemoteSwiftPackageReference "HTMLStreamer" */, @@ -3274,14 +3267,6 @@ minimumVersion = 1.0.1; }; }; - D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/karwa/swift-url"; - requirement = { - kind = exactVersion; - version = 0.4.2; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3319,11 +3304,6 @@ package = D60BB3922B30076F00DAEA65 /* XCRemoteSwiftPackageReference "HTMLStreamer" */; productName = HTMLStreamer; }; - D630C4242BC7845800208903 /* WebURL */ = { - isa = XCSwiftPackageProductDependency; - package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */; - productName = WebURL; - }; D635237029B78A7D009ED5E7 /* TuskerComponents */ = { isa = XCSwiftPackageProductDependency; productName = TuskerComponents; @@ -3342,11 +3322,6 @@ isa = XCSwiftPackageProductDependency; productName = TTTKit; }; - D6676CA427A8D0020052936B /* WebURLFoundationExtras */ = { - isa = XCSwiftPackageProductDependency; - package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */; - productName = WebURLFoundationExtras; - }; D674A50827F9128D00BA03AC /* Pachyderm */ = { isa = XCSwiftPackageProductDependency; productName = Pachyderm; diff --git a/Tusker/CoreData/FollowedHashtag.swift b/Tusker/CoreData/FollowedHashtag.swift index 22156e275..b4fb4b812 100644 --- a/Tusker/CoreData/FollowedHashtag.swift +++ b/Tusker/CoreData/FollowedHashtag.swift @@ -9,7 +9,6 @@ import Foundation import CoreData import Pachyderm -import WebURLFoundationExtras @objc(FollowedHashtag) public final class FollowedHashtag: NSManagedObject { @@ -33,6 +32,6 @@ extension FollowedHashtag { convenience init(hashtag: Hashtag, context: NSManagedObjectContext) { self.init(context: context) self.name = hashtag.name - self.url = URL(hashtag.url)! + self.url = hashtag.url } } diff --git a/Tusker/CoreData/SavedHashtag.swift b/Tusker/CoreData/SavedHashtag.swift index f206d4564..446033cca 100644 --- a/Tusker/CoreData/SavedHashtag.swift +++ b/Tusker/CoreData/SavedHashtag.swift @@ -9,7 +9,6 @@ import Foundation import CoreData import Pachyderm -import WebURLFoundationExtras import UserAccounts @objc(SavedHashtag) @@ -42,6 +41,6 @@ extension SavedHashtag { self.init(context: context) self.accountID = account.id self.name = hashtag.name - self.url = URL(hashtag.url)! + self.url = hashtag.url } } diff --git a/Tusker/CoreData/StatusMO.swift b/Tusker/CoreData/StatusMO.swift index 364480ccc..fd183e409 100644 --- a/Tusker/CoreData/StatusMO.swift +++ b/Tusker/CoreData/StatusMO.swift @@ -10,7 +10,6 @@ import Foundation import CoreData import Pachyderm -import WebURLFoundationExtras @objc(StatusMO) public final class StatusMO: NSManagedObject, StatusProtocol { @@ -136,7 +135,7 @@ extension StatusMO { self.sensitive = status.sensitive self.spoilerText = status.spoilerText self.uri = status.uri - self.url = status.url != nil ? URL(status.url!) : nil + self.url = status.url self.visibility = status.visibility self.poll = status.poll self.localOnly = status.localOnly ?? false diff --git a/Tusker/HTMLConverter.swift b/Tusker/HTMLConverter.swift index c1975d232..68a995b1f 100644 --- a/Tusker/HTMLConverter.swift +++ b/Tusker/HTMLConverter.swift @@ -8,8 +8,7 @@ import UIKit import HTMLStreamer -import WebURL -import WebURLFoundationExtras +import Pachyderm class HTMLConverter { @@ -45,17 +44,7 @@ extension HTMLConverter { // note: this is duplicated in NotificationExtension struct Callbacks: HTMLConversionCallbacks { static func makeURL(string: String) -> URL? { - // Converting WebURL to URL is a small but non-trivial expense (since it works by - // serializing the WebURL as a string and then having Foundation parse it again), - // so, if available, use the system parser which doesn't require another round trip. - if let url = try? URL.ParseStrategy().parse(string) { - url - } else if let web = WebURL(string), - let url = URL(web) { - url - } else { - nil - } + try? URL.ParseStrategy().parse(string) } static func elementAction(name: String, attributes: [Attribute]) -> ElementAction { diff --git a/Tusker/Screens/Announcements/AnnouncementContentTextView.swift b/Tusker/Screens/Announcements/AnnouncementContentTextView.swift index cc9d15db4..792b1ae19 100644 --- a/Tusker/Screens/Announcements/AnnouncementContentTextView.swift +++ b/Tusker/Screens/Announcements/AnnouncementContentTextView.swift @@ -8,7 +8,6 @@ import UIKit import Pachyderm -import WebURL class AnnouncementContentTextView: ContentTextView { @@ -30,7 +29,7 @@ class AnnouncementContentTextView: ContentTextView { override func getMention(for url: URL, text: String) -> Mention? { announcement?.mentions.first { - URL($0.url) == url + $0.url == url }.map { Mention(url: $0.url, username: $0.username, acct: $0.acct, id: $0.id) } @@ -38,7 +37,7 @@ class AnnouncementContentTextView: ContentTextView { override func getHashtag(for url: URL, text: String) -> Hashtag? { announcement?.tags.first { - URL($0.url) == url + $0.url == url } } diff --git a/Tusker/Screens/Announcements/AnnouncementListRow.swift b/Tusker/Screens/Announcements/AnnouncementListRow.swift index 13371c20f..e62a786a0 100644 --- a/Tusker/Screens/Announcements/AnnouncementListRow.swift +++ b/Tusker/Screens/Announcements/AnnouncementListRow.swift @@ -9,7 +9,6 @@ import SwiftUI import Pachyderm import TuskerComponents -import WebURLFoundationExtras struct AnnouncementListRow: View { @Binding var announcement: Announcement @@ -116,8 +115,8 @@ struct AnnouncementListRow: View { let url: URL? let staticURL: URL? if case .custom(let emoji) = reaction { - url = URL(emoji.url) - staticURL = URL(emoji.staticURL) + url = emoji.url + staticURL = emoji.staticURL } else { url = nil staticURL = nil diff --git a/Tusker/Screens/Conversation/ConversationViewController.swift b/Tusker/Screens/Conversation/ConversationViewController.swift index 53c8b92bc..933d8c9b3 100644 --- a/Tusker/Screens/Conversation/ConversationViewController.swift +++ b/Tusker/Screens/Conversation/ConversationViewController.swift @@ -8,8 +8,6 @@ import UIKit import Pachyderm -import WebURL -import WebURLFoundationExtras private let mastodonRemoteStatusRegex = try! NSRegularExpression(pattern: "^/@.+@.+/\\d{18}") private func isLikelyMastodonRemoteStatus(url: URL) -> Bool { @@ -229,10 +227,10 @@ class ConversationViewController: UIViewController { let location = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "location") { effectiveURL = location } else { - effectiveURL = WebURL(url)!.serialized(excludingFragment: true) + effectiveURL = url.formatted(.url.fragment(.never)) } } else { - effectiveURL = WebURL(url)!.serialized(excludingFragment: true) + effectiveURL = url.formatted(.url.fragment(.never)) } let request = Client.search(query: effectiveURL, types: [.statuses], resolve: true) diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 1534baa7a..dbf0e2be5 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -10,7 +10,6 @@ import UIKit import Combine import Pachyderm import CoreData -import WebURLFoundationExtras class ExploreViewController: UIViewController, UICollectionViewDelegate, CollectionViewController { @@ -560,10 +559,7 @@ extension ExploreViewController: UICollectionViewDragDelegate { activity.displaysAuxiliaryScene = true provider = NSItemProvider(object: activity) case let .savedHashtag(hashtag): - guard let url = URL(hashtag.url) else { - return [] - } - provider = NSItemProvider(object: url as NSURL) + provider = NSItemProvider(object: hashtag.url as NSURL) if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: accountID) { activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) diff --git a/Tusker/Screens/Explore/TrendingHashtagsViewController.swift b/Tusker/Screens/Explore/TrendingHashtagsViewController.swift index 28691fa4d..49f039e25 100644 --- a/Tusker/Screens/Explore/TrendingHashtagsViewController.swift +++ b/Tusker/Screens/Explore/TrendingHashtagsViewController.swift @@ -8,7 +8,6 @@ import UIKit import Pachyderm -import WebURLFoundationExtras import Combine class TrendingHashtagsViewController: UIViewController, CollectionViewController { @@ -277,11 +276,10 @@ extension TrendingHashtagsViewController: UICollectionViewDelegate { extension TrendingHashtagsViewController: UICollectionViewDragDelegate { func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { guard let item = dataSource.itemIdentifier(for: indexPath), - case let .tag(hashtag) = item, - let url = URL(hashtag.url) else { + case let .tag(hashtag) = item else { return [] } - let provider = NSItemProvider(object: url as NSURL) + let provider = NSItemProvider(object: hashtag.url as NSURL) if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: mastodonController.accountInfo!.id) { activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) diff --git a/Tusker/Screens/Explore/TrendingLinkCardCollectionViewCell.swift b/Tusker/Screens/Explore/TrendingLinkCardCollectionViewCell.swift index 3d2c22d35..dd06dba80 100644 --- a/Tusker/Screens/Explore/TrendingLinkCardCollectionViewCell.swift +++ b/Tusker/Screens/Explore/TrendingLinkCardCollectionViewCell.swift @@ -9,7 +9,6 @@ #if !os(visionOS) import UIKit import Pachyderm -import WebURLFoundationExtras import HTMLStreamer class TrendingLinkCardCollectionViewCell: UICollectionViewCell { @@ -71,7 +70,7 @@ class TrendingLinkCardCollectionViewCell: UICollectionViewCell { self.card = card self.thumbnailView.image = nil - thumbnailView.update(for: card.image.flatMap { URL($0) }, blurhash: card.blurhash) + thumbnailView.update(for: card.image, blurhash: card.blurhash) let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines) titleLabel.text = title diff --git a/Tusker/Screens/Explore/TrendingLinkCardView.swift b/Tusker/Screens/Explore/TrendingLinkCardView.swift index 735f1257f..424256b2d 100644 --- a/Tusker/Screens/Explore/TrendingLinkCardView.swift +++ b/Tusker/Screens/Explore/TrendingLinkCardView.swift @@ -9,7 +9,6 @@ #if os(visionOS) import SwiftUI import Pachyderm -import WebURLFoundationExtras import HTMLStreamer struct TrendingLinkCardView: View { diff --git a/Tusker/Screens/Explore/TrendingLinksViewController.swift b/Tusker/Screens/Explore/TrendingLinksViewController.swift index af73cbdf5..d390c41fb 100644 --- a/Tusker/Screens/Explore/TrendingLinksViewController.swift +++ b/Tusker/Screens/Explore/TrendingLinksViewController.swift @@ -8,7 +8,6 @@ import UIKit import Pachyderm -import WebURLFoundationExtras import SafariServices import Combine #if os(visionOS) @@ -293,21 +292,19 @@ extension TrendingLinksViewController: UICollectionViewDelegate { } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard case .link(let card) = dataSource.itemIdentifier(for: indexPath), - let url = URL(card.url) else { + guard case .link(let card) = dataSource.itemIdentifier(for: indexPath) else { return } - selected(url: url) + selected(url: card.url) } func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { guard case .link(let card) = dataSource.itemIdentifier(for: indexPath), - let url = URL(card.url), let cell = collectionView.cellForItem(at: indexPath) else { return nil } return UIContextMenuConfiguration { - let vc = SFSafariViewController(url: url) + let vc = SFSafariViewController(url: card.url) #if !os(visionOS) vc.preferredControlTintColor = Preferences.shared.accentColor.color #endif @@ -324,11 +321,10 @@ extension TrendingLinksViewController: UICollectionViewDelegate { extension TrendingLinksViewController: UICollectionViewDragDelegate { func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - guard case .link(let card) = dataSource.itemIdentifier(for: indexPath), - let url = URL(card.url) else { + guard case .link(let card) = dataSource.itemIdentifier(for: indexPath) else { return [] } - return [UIDragItem(itemProvider: NSItemProvider(object: url as NSURL))] + return [UIDragItem(itemProvider: NSItemProvider(object: card.url as NSURL))] } } diff --git a/Tusker/Screens/Explore/TrendsViewController.swift b/Tusker/Screens/Explore/TrendsViewController.swift index 511c22b8f..96223bc26 100644 --- a/Tusker/Screens/Explore/TrendsViewController.swift +++ b/Tusker/Screens/Explore/TrendsViewController.swift @@ -513,9 +513,7 @@ extension TrendsViewController: UICollectionViewDelegate { show(HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController), sender: nil) case let .link(card): - if let url = URL(card.url) { - selected(url: url) - } + selected(url: card.url) case let .status(id, state): selected(status: id, state: state.copy()) @@ -544,12 +542,9 @@ extension TrendsViewController: UICollectionViewDelegate { } case let .link(card): - guard let url = URL(card.url) else { - return nil - } let cell = collectionView.cellForItem(at: indexPath)! return UIContextMenuConfiguration { - let vc = SFSafariViewController(url: url) + let vc = SFSafariViewController(url: card.url) #if !os(visionOS) vc.preferredControlTintColor = Preferences.shared.accentColor.color #endif @@ -624,10 +619,7 @@ extension TrendsViewController: UICollectionViewDragDelegate { return [] case let .tag(hashtag): - guard let url = URL(hashtag.url) else { - return [] - } - let provider = NSItemProvider(object: url as NSURL) + let provider = NSItemProvider(object: hashtag.url as NSURL) if let activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: hashtag.name), accountID: mastodonController.accountInfo!.id) { activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) @@ -635,10 +627,7 @@ extension TrendsViewController: UICollectionViewDragDelegate { return [UIDragItem(itemProvider: provider)] case let .link(card): - guard let url = URL(card.url) else { - return [] - } - return [UIDragItem(itemProvider: NSItemProvider(object: url as NSURL))] + return [UIDragItem(itemProvider: NSItemProvider(object: card.url as NSURL))] case let .status(id, _): guard let status = mastodonController.persistentContainer.status(for: id), diff --git a/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift b/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift index a4de09d00..e43787478 100644 --- a/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift +++ b/Tusker/Screens/Fast Account Switcher/FastSwitchingAccountView.swift @@ -8,7 +8,6 @@ import UIKit import UserAccounts -import WebURL class FastSwitchingAccountView: UIView { @@ -131,11 +130,7 @@ class FastSwitchingAccountView: UIView { private func setupAccount(account: UserAccountInfo) { usernameLabel.text = account.username - if let domain = WebURL.Domain(account.instanceURL.host!) { - instanceLabel.text = domain.render(.uncheckedUnicodeString) - } else { - instanceLabel.text = account.instanceURL.host! - } + instanceLabel.text = account.instanceURL.host(percentEncoded: false) let controller = MastodonController.getForAccount(account) avatarTask = Task { guard let account = try? await controller.getOwnAccount(), diff --git a/Tusker/Screens/Notifications/ActionNotificationGroupCollectionViewCell.swift b/Tusker/Screens/Notifications/ActionNotificationGroupCollectionViewCell.swift index 1494c4fa1..9c4e7bf43 100644 --- a/Tusker/Screens/Notifications/ActionNotificationGroupCollectionViewCell.swift +++ b/Tusker/Screens/Notifications/ActionNotificationGroupCollectionViewCell.swift @@ -155,7 +155,7 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell { fetchCustomEmojiImage?.1.cancel() case .emojiReaction(let emojiOrShortcode, let url): iconImageView.image = nil - if let url = url.flatMap({ URL($0) }), + if let url, fetchCustomEmojiImage?.0 != url { fetchCustomEmojiImage?.1.cancel() let task = Task { diff --git a/Tusker/Screens/Notifications/NotificationsCollectionViewController.swift b/Tusker/Screens/Notifications/NotificationsCollectionViewController.swift index acd5b01a9..9643aa14c 100644 --- a/Tusker/Screens/Notifications/NotificationsCollectionViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsCollectionViewController.swift @@ -740,7 +740,7 @@ extension NotificationsCollectionViewController: UICollectionViewDragDelegate { return cell.dragItemsForBeginning(session: session) case .poll, .update: let status = group.notifications.first!.status! - let provider = NSItemProvider(object: URL(status.url!)! as NSURL) + let provider = NSItemProvider(object: status.url! as NSURL) let activity = UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: mastodonController.accountInfo!.id) activity.displaysAuxiliaryScene = true provider.registerObject(activity, visibility: .all) diff --git a/Tusker/Screens/Preferences/Appearance/MockStatusView.swift b/Tusker/Screens/Preferences/Appearance/MockStatusView.swift index 5cbd46e25..30fcc5abf 100644 --- a/Tusker/Screens/Preferences/Appearance/MockStatusView.swift +++ b/Tusker/Screens/Preferences/Appearance/MockStatusView.swift @@ -8,7 +8,6 @@ import SwiftUI import Pachyderm -import WebURL struct MockStatusView: View { @ObservedObject private var preferences = Preferences.shared @@ -136,8 +135,8 @@ private struct MockStatusCardView: UIViewRepresentable { let view = StatusCardView() view.isUserInteractionEnabled = false let card = StatusCardView.CardData( - url: WebURL("https://vaccor.space/tusker")!, - image: WebURL("https://vaccor.space/tusker/img/icon.png")!, + url: URL(string: "https://vaccor.space/tusker")!, + image: URL(string: "https://vaccor.space/tusker/img/icon.png")!, title: "Tusker", description: "Tusker is an iOS app for Mastodon" ) diff --git a/Tusker/Screens/Preferences/PrefsAccountView.swift b/Tusker/Screens/Preferences/PrefsAccountView.swift index 8eda85ff2..595ef095c 100644 --- a/Tusker/Screens/Preferences/PrefsAccountView.swift +++ b/Tusker/Screens/Preferences/PrefsAccountView.swift @@ -8,7 +8,6 @@ import SwiftUI import UserAccounts -import WebURL struct PrefsAccountView: View { let account: UserAccountInfo @@ -19,12 +18,7 @@ struct PrefsAccountView: View { VStack(alignment: .prefsAvatar) { Text(verbatim: account.username) .foregroundColor(.primary) - let instance = if let domain = WebURL.Domain(account.instanceURL.host!) { - domain.render(.uncheckedUnicodeString) - } else { - account.instanceURL.host! - } - Text(verbatim: instance) + Text(verbatim: account.instanceURL.host(percentEncoded: false)!) .font(.caption) .foregroundColor(.primary) } diff --git a/Tusker/Screens/Search/SearchResultsViewController.swift b/Tusker/Screens/Search/SearchResultsViewController.swift index 714769d85..c0f5224de 100644 --- a/Tusker/Screens/Search/SearchResultsViewController.swift +++ b/Tusker/Screens/Search/SearchResultsViewController.swift @@ -9,7 +9,6 @@ import UIKit import Combine import Pachyderm -import WebURLFoundationExtras fileprivate let accountCell = "accountCell" fileprivate let statusCell = "statusCell" @@ -538,7 +537,7 @@ extension SearchResultsViewController: UICollectionViewDragDelegate { url = account.url activity = UserActivityManager.showProfileActivity(id: id, accountID: accountInfo.id) case .hashtag(let tag): - url = URL(tag.url)! + url = tag.url activity = UserActivityManager.showTimelineActivity(timeline: .tag(hashtag: tag.name), accountID: accountInfo.id)! case .status(let id, _): guard let status = mastodonController.persistentContainer.status(for: id), diff --git a/Tusker/Screens/Status Action Account List/StatusActionAccountListViewController.swift b/Tusker/Screens/Status Action Account List/StatusActionAccountListViewController.swift index b2ea25a3f..afeb92901 100644 --- a/Tusker/Screens/Status Action Account List/StatusActionAccountListViewController.swift +++ b/Tusker/Screens/Status Action Account List/StatusActionAccountListViewController.swift @@ -8,7 +8,6 @@ import UIKit import Pachyderm -import WebURL class StatusActionAccountListViewController: UIViewController { @@ -183,7 +182,7 @@ extension StatusActionAccountListViewController { enum ActionType { case favorite case reblog - case emojiReaction(String, WebURL?) + case emojiReaction(String, URL?) init?(_ groupKind: NotificationGroup.Kind) { switch groupKind { diff --git a/Tusker/Screens/Utilities/Previewing.swift b/Tusker/Screens/Utilities/Previewing.swift index 5b9801cb1..927c4799c 100644 --- a/Tusker/Screens/Utilities/Previewing.swift +++ b/Tusker/Screens/Utilities/Previewing.swift @@ -9,7 +9,6 @@ import UIKit import SafariServices import Pachyderm -import WebURLFoundationExtras import SwiftUI @MainActor @@ -154,12 +153,7 @@ extension MenuActionProvider { } } - let shareSection: [UIMenuElement] - if let url = URL(hashtag.url) { - shareSection = actionsForURL(url, source: source) - } else { - shareSection = [] - } + let shareSection = actionsForURL(hashtag.url, source: source) return [ UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection), @@ -375,14 +369,11 @@ extension MenuActionProvider { } func actionsForTrendingLink(card: Card, source: PopoverSource) -> [UIMenuElement] { - guard let url = URL(card.url) else { - return [] - } return [ - openInSafariAction(url: url), + openInSafariAction(url: card.url), createAction(identifier: "share", title: "Share…", systemImageName: "square.and.arrow.up", handler: { [weak self] (_) in guard let self else { return } - self.navigationDelegate?.showMoreOptions(forURL: url, source: source) + self.navigationDelegate?.showMoreOptions(forURL: card.url, source: source) }), createAction(identifier: "postlink", title: "Post this Link", systemImageName: "square.and.pencil", handler: { [weak self] _ in guard let self = self else { return } @@ -393,7 +384,7 @@ extension MenuActionProvider { text += title text += ":\n" } - text += url.absoluteString + text += card.url.absoluteString let draft = self.mastodonController!.createDraft(text: text) self.navigationDelegate?.compose(editing: draft) diff --git a/Tusker/Views/AccountDisplayNameView.swift b/Tusker/Views/AccountDisplayNameView.swift index 2cda0c0f7..7b58395e3 100644 --- a/Tusker/Views/AccountDisplayNameView.swift +++ b/Tusker/Views/AccountDisplayNameView.swift @@ -8,7 +8,6 @@ import SwiftUI import Pachyderm -import WebURLFoundationExtras import os private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) @@ -54,7 +53,7 @@ struct AccountDisplayNameView: View { } group.enter() - let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in + let request = ImageCache.emojis.get(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 92b0d6e4b..162679128 100644 --- a/Tusker/Views/BaseEmojiLabel.swift +++ b/Tusker/Views/BaseEmojiLabel.swift @@ -8,7 +8,6 @@ import UIKit import Pachyderm -import WebURLFoundationExtras import os private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) @@ -73,7 +72,7 @@ extension BaseEmojiLabel { foundEmojis = true - if let image = ImageCache.emojis.get(URL(emoji.url)!)?.image { + if let image = ImageCache.emojis.get(emoji.url)?.image { // if the image is cached, add it immediately. // we generate the thumbnail on the main thread, because it's usually fast enough // and the delay caused by doing it asynchronously looks works. @@ -90,7 +89,7 @@ extension BaseEmojiLabel { // otherwise, perform the network request group.enter() - let request = ImageCache.emojis.getFromSource(URL(emoji.url)!) { (_, image) in + let request = ImageCache.emojis.getFromSource(emoji.url) { (_, image) in guard let image else { group.leave() return @@ -98,7 +97,7 @@ extension BaseEmojiLabel { image.prepareThumbnail(of: emojiImageSize(image)) { thumbnail in guard let thumbnail = thumbnail?.cgImage, case let rescaled = UIImage(cgImage: thumbnail, scale: screenScale, orientation: .up), - let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: rescaled) else { + let transformedImage = ImageGrayscalifier.convertIfNecessary(url: emoji.url, image: rescaled) else { group.leave() return } diff --git a/Tusker/Views/ContentTextView.swift b/Tusker/Views/ContentTextView.swift index d2a2e5041..41230698c 100644 --- a/Tusker/Views/ContentTextView.swift +++ b/Tusker/Views/ContentTextView.swift @@ -9,8 +9,6 @@ import UIKit import Pachyderm import SafariServices -import WebURL -import WebURLFoundationExtras import Combine private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) diff --git a/Tusker/Views/CustomEmojiImageView.swift b/Tusker/Views/CustomEmojiImageView.swift index 9155696dc..ae784df93 100644 --- a/Tusker/Views/CustomEmojiImageView.swift +++ b/Tusker/Views/CustomEmojiImageView.swift @@ -8,7 +8,6 @@ import SwiftUI import Pachyderm -import WebURLFoundationExtras struct CustomEmojiImageView: View { let emoji: Emoji @@ -35,7 +34,7 @@ struct CustomEmojiImageView: View { @MainActor private func loadImage() { - request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in + request = ImageCache.emojis.get(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 a8feb6bd7..40f8ee44d 100644 --- a/Tusker/Views/Status/StatusCardView.swift +++ b/Tusker/Views/Status/StatusCardView.swift @@ -9,8 +9,6 @@ import UIKit import Pachyderm import SafariServices -import WebURL -import WebURLFoundationExtras import HTMLStreamer class StatusCardView: UIView { @@ -184,14 +182,14 @@ class StatusCardView: UIView { if sensitive { if let blurhash = card.blurhash { imageView.blurImage = false - imageView.showOnlyBlurHash(blurhash, for: URL(image)!) + imageView.showOnlyBlurHash(blurhash, for: image) } else { // if we don't have a blurhash, load the image and show it behind a blur imageView.blurImage = true - imageView.update(for: URL(image), blurhash: nil) + imageView.update(for: image, blurhash: nil) } } else { - imageView.update(for: URL(image), blurhash: card.blurhash) + imageView.update(for: image, blurhash: card.blurhash) } imageView.isHidden = false leadingSpacer.isHidden = true @@ -210,8 +208,8 @@ class StatusCardView: UIView { descriptionLabel.text = description descriptionLabel.isHidden = description.isEmpty - if let host = card.url.host { - domainLabel.text = host.serialized + if let host = card.url.host(percentEncoded: false) { + domainLabel.text = host domainLabel.isHidden = false } else { domainLabel.isHidden = true @@ -238,7 +236,7 @@ class StatusCardView: UIView { setNeedsDisplay() if let card = card, let delegate = navigationDelegate { - delegate.selected(url: URL(card.url)!) + delegate.selected(url: card.url) } } @@ -248,8 +246,8 @@ class StatusCardView: UIView { } struct CardData: Equatable { - let url: WebURL - let image: WebURL? + let url: URL + let image: URL? let title: String let description: String let blurhash: String? @@ -262,7 +260,7 @@ class StatusCardView: UIView { self.blurhash = card.blurhash } - init(url: WebURL, image: WebURL? = nil, title: String, description: String, blurhash: String? = nil) { + init(url: URL, image: URL? = nil, title: String, description: String, blurhash: String? = nil) { self.url = url self.image = image self.title = title @@ -278,13 +276,13 @@ extension StatusCardView: UIContextMenuInteractionDelegate { guard let card = card else { return nil } return UIContextMenuConfiguration(identifier: nil) { - let vc = SFSafariViewController(url: URL(card.url)!) + let vc = SFSafariViewController(url: card.url) #if !os(visionOS) vc.preferredControlTintColor = Preferences.shared.accentColor.color #endif return vc } actionProvider: { (_) in - let actions = self.actionProvider?.actionsForURL(URL(card.url)!, source: .view(self)) ?? [] + let actions = self.actionProvider?.actionsForURL(card.url, source: .view(self)) ?? [] return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions) } } diff --git a/Tusker/Views/StatusContentTextView.swift b/Tusker/Views/StatusContentTextView.swift index 1fdaee45c..d7458c510 100644 --- a/Tusker/Views/StatusContentTextView.swift +++ b/Tusker/Views/StatusContentTextView.swift @@ -8,7 +8,6 @@ import UIKit import Pachyderm -import WebURLFoundationExtras class StatusContentTextView: ContentTextView { @@ -26,7 +25,7 @@ class StatusContentTextView: ContentTextView { let mastodonController = mastodonController, let status = mastodonController.persistentContainer.status(for: statusID) { mention = status.mentions.first { (mention) in - url.host == mention.url.host!.serialized && ( + url.host() == mention.url.host() && ( text.dropFirst() == mention.username // Mastodon and Pleroma include @ in the text || text.dropFirst() == mention.acct // Misskey includes @ and uses the whole acct || text == mention.username // GNU Social does not include the @ in the text, so we don't need to drop it @@ -44,7 +43,7 @@ class StatusContentTextView: ContentTextView { let mastodonController = mastodonController, let status = mastodonController.persistentContainer.status(for: statusID) { hashtag = status.hashtags.first { (hashtag) in - URL(hashtag.url) == url + hashtag.url == url } } else { hashtag = nil