// 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 /// The status overview, and from it one host or the notifications page. case status case host(id: String) case notifications static let scheme = "wristcode" /// `wristcode://thread//`, `wristcode://new`, `wristcode://status`, /// `wristcode://host/` or `wristcode://notifications`. var url: URL { var components = URLComponents() components.scheme = Self.scheme switch self { case let .thread(hostId, threadId): components.host = "thread" components.percentEncodedPath = "/" + [hostId, threadId] .map { $0.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? $0 } .joined(separator: "/") case .newThread: components.host = "new" case .status: components.host = "status" case let .host(id): components.host = "host" components.percentEncodedPath = "/" + (id.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? id) case .notifications: components.host = "notifications" } 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 "status": self = .status case "host" where parts.count == 1 && !parts[0].isEmpty: self = .host(id: parts[0]) case "notifications": self = .notifications default: return nil } } }