summaryrefslogtreecommitdiff
path: root/Hutch/Extensions
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-03-17 23:19:22 -0500
committerChristian Cleberg <[email protected]>2026-03-17 23:19:22 -0500
commit8f2057c53e9009c2529c9c4849c914666c0e4b40 (patch)
tree77b5a93625fe9ea98baf4ab0d137055524705dad /Hutch/Extensions
parentd27726d8755a5631fbd2105f735811a95379c4ab (diff)
downloadhutch-8f2057c53e9009c2529c9c4849c914666c0e4b40.tar.gz
hutch-8f2057c53e9009c2529c9c4849c914666c0e4b40.tar.bz2
hutch-8f2057c53e9009c2529c9c4849c914666c0e4b40.zip
create privacy policy
Diffstat (limited to 'Hutch/Extensions')
-rw-r--r--Hutch/Extensions/SRHTShareUI.swift57
-rw-r--r--Hutch/Extensions/SRHTWebURL.swift84
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
+ }()
+}