diff options
Diffstat (limited to 'Hutch/Extensions')
| -rw-r--r-- | Hutch/Extensions/SRHTShareUI.swift | 57 | ||||
| -rw-r--r-- | Hutch/Extensions/SRHTWebURL.swift | 84 |
2 files changed, 141 insertions, 0 deletions
diff --git a/Hutch/Extensions/SRHTShareUI.swift b/Hutch/Extensions/SRHTShareUI.swift new file mode 100644 index 0000000..9d949c9 --- /dev/null +++ b/Hutch/Extensions/SRHTShareUI.swift @@ -0,0 +1,57 @@ +import SwiftUI +import UIKit + +enum SRHTShareTarget: String { + case repository = "repository" + case commit = "commit" + case file = "file" + case build = "build" + case tracker = "tracker" + case ticket = "ticket" + case profile = "profile" + + var fallbackMessage: String { + "This \(rawValue) does not have a valid web URL to share." + } +} + +struct SRHTShareButton<Label: View>: View { + let url: URL? + let target: SRHTShareTarget + @ViewBuilder let label: () -> Label + + @State private var isShowingShareSheet = false + @State private var isShowingFallbackAlert = false + + var body: some View { + Button { + if url != nil { + isShowingShareSheet = true + } else { + isShowingFallbackAlert = true + } + } label: { + label() + } + .sheet(isPresented: $isShowingShareSheet) { + if let url { + ShareSheet(activityItems: [url]) + } + } + .alert("Share Unavailable", isPresented: $isShowingFallbackAlert) { + Button("OK", role: .cancel) {} + } message: { + Text(target.fallbackMessage) + } + } +} + +private struct ShareSheet: UIViewControllerRepresentable { + let activityItems: [Any] + + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} +} diff --git a/Hutch/Extensions/SRHTWebURL.swift b/Hutch/Extensions/SRHTWebURL.swift new file mode 100644 index 0000000..53556de --- /dev/null +++ b/Hutch/Extensions/SRHTWebURL.swift @@ -0,0 +1,84 @@ +import Foundation + +enum SRHTWebURL { + static func repository(_ repository: RepositorySummary) -> URL? { + userScopedURL( + host: "\(repository.service.rawValue).sr.ht", + ownerCanonicalName: repository.owner.canonicalName, + pathComponents: [repository.name] + ) + } + + static func build(jobId: Int, ownerCanonicalName: String) -> URL? { + userScopedURL( + host: "builds.sr.ht", + ownerCanonicalName: ownerCanonicalName, + pathComponents: ["job", String(jobId)] + ) + } + + static func tracker(ownerUsername: String, trackerName: String) -> URL? { + userScopedURL( + host: "todo.sr.ht", + ownerUsername: ownerUsername, + pathComponents: [trackerName] + ) + } + + static func ticket(ownerUsername: String, trackerName: String, ticketId: Int) -> URL? { + userScopedURL( + host: "todo.sr.ht", + ownerUsername: ownerUsername, + pathComponents: [trackerName, String(ticketId)] + ) + } + + static func profile(canonicalName: String) -> URL? { + userScopedURL( + host: "meta.sr.ht", + ownerCanonicalName: canonicalName, + pathComponents: [] + ) + } + + private static func userScopedURL( + host: String, + ownerCanonicalName: String, + pathComponents: [String] + ) -> URL? { + userScopedURL( + host: host, + ownerUsername: username(from: ownerCanonicalName), + pathComponents: pathComponents + ) + } + + private static func userScopedURL( + host: String, + ownerUsername: String, + pathComponents: [String] + ) -> URL? { + var components = URLComponents() + components.scheme = "https" + components.host = host + + let encodedComponents = (["~\(ownerUsername)"] + pathComponents).map { pathComponent in + pathComponent.addingPercentEncoding(withAllowedCharacters: pathComponentCharacterSet) ?? pathComponent + } + components.percentEncodedPath = "/" + encodedComponents.joined(separator: "/") + return components.url + } + + private static func username(from canonicalName: String) -> String { + if canonicalName.hasPrefix("~") { + return String(canonicalName.dropFirst()) + } + return canonicalName + } + + private static let pathComponentCharacterSet: CharacterSet = { + var characterSet = CharacterSet.urlPathAllowed + characterSet.remove(charactersIn: "/") + return characterSet + }() +} |
