105 lines
4.2 KiB
Swift
105 lines
4.2 KiB
Swift
// Screens the app can open, and the `wristcode://` URLs the complication uses to open them.
|
|
|
|
import Foundation
|
|
|
|
enum Route: Hashable, Sendable {
|
|
case thread(hostId: String, threadId: String)
|
|
case newThread
|
|
/// Every thread in Recent, past the feed's first 10.
|
|
case recent
|
|
/// Finished threads snoozed for later.
|
|
case snoozed
|
|
/// The status overview, and from it one host or the notifications page.
|
|
case status
|
|
case host(id: String)
|
|
case notifications
|
|
/// All threads: every host, then one host's projects, then one project's threads.
|
|
case allThreads
|
|
case hostProjects(hostId: String)
|
|
case projectThreads(hostId: String, projectId: String)
|
|
/// Threads on every host matching a search from All threads.
|
|
case search(query: String)
|
|
|
|
static let scheme = "wristcode"
|
|
/// The fewest characters the gateway searches for.
|
|
static let minimumSearch = 2
|
|
|
|
/// `wristcode://thread/<hostId>/<threadId>`, `wristcode://new`, `wristcode://recent`, `wristcode://snoozed`,
|
|
/// `wristcode://status`,
|
|
/// `wristcode://host/<hostId>`, `wristcode://notifications`, `wristcode://threads`,
|
|
/// `wristcode://projects/<hostId>`, `wristcode://project/<hostId>/<projectId>` or `wristcode://search?q=<query>`.
|
|
var url: URL {
|
|
var components = URLComponents()
|
|
components.scheme = Self.scheme
|
|
func path(_ parts: String...) -> String {
|
|
"/" + parts.map { $0.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? $0 }.joined(separator: "/")
|
|
}
|
|
switch self {
|
|
case let .thread(hostId, threadId):
|
|
components.host = "thread"
|
|
components.percentEncodedPath = path(hostId, threadId)
|
|
case .newThread:
|
|
components.host = "new"
|
|
case .recent:
|
|
components.host = "recent"
|
|
case .snoozed:
|
|
components.host = "snoozed"
|
|
case .status:
|
|
components.host = "status"
|
|
case let .host(id):
|
|
components.host = "host"
|
|
components.percentEncodedPath = path(id)
|
|
case .notifications:
|
|
components.host = "notifications"
|
|
case .allThreads:
|
|
components.host = "threads"
|
|
case let .hostProjects(hostId):
|
|
components.host = "projects"
|
|
components.percentEncodedPath = path(hostId)
|
|
case let .projectThreads(hostId, projectId):
|
|
components.host = "project"
|
|
components.percentEncodedPath = path(hostId, projectId)
|
|
case let .search(query):
|
|
components.host = "search"
|
|
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
|
}
|
|
return components.url!
|
|
}
|
|
|
|
/// The route a `wristcode://` URL names, or nil when it names none.
|
|
init?(url: URL) {
|
|
guard url.scheme == Self.scheme,
|
|
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
|
|
else { return nil }
|
|
let parts = components.percentEncodedPath.split(separator: "/").map { $0.removingPercentEncoding ?? String($0) }
|
|
switch components.host {
|
|
case "thread" where parts.count == 2 && parts.allSatisfy { !$0.isEmpty }:
|
|
self = .thread(hostId: parts[0], threadId: parts[1])
|
|
case "new":
|
|
self = .newThread
|
|
case "recent":
|
|
self = .recent
|
|
case "snoozed":
|
|
self = .snoozed
|
|
case "status":
|
|
self = .status
|
|
case "host" where parts.count == 1 && !parts[0].isEmpty:
|
|
self = .host(id: parts[0])
|
|
case "notifications":
|
|
self = .notifications
|
|
case "threads":
|
|
self = .allThreads
|
|
case "projects" where parts.count == 1 && !parts[0].isEmpty:
|
|
self = .hostProjects(hostId: parts[0])
|
|
case "project" where parts.count == 2 && parts.allSatisfy { !$0.isEmpty }:
|
|
self = .projectThreads(hostId: parts[0], projectId: parts[1])
|
|
case "search":
|
|
guard let query = components.queryItems?.first(where: { $0.name == "q" })?.value, query.count >= Self.minimumSearch
|
|
else { return nil }
|
|
self = .search(query: query)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|