diff --git a/Gemini/ContentView.swift b/Gemini/ContentView.swift index 96db521..3138c22 100644 --- a/Gemini/ContentView.swift +++ b/Gemini/ContentView.swift @@ -13,8 +13,7 @@ import GeminiProtocol struct ContentView: View { let navigator: NavigationManager @State var task: GeminiDataTask? - @State var document: Document? - @State var errorMessage: String? + @State var state: ViewState = .loading var body: some View { mainView @@ -24,17 +23,18 @@ struct ContentView: View { @ViewBuilder private var mainView: some View { - if let document = document { - DocumentView(document: document, changeURL: navigator.changeURL) - } else if let errorMessage = errorMessage { + switch state { + case .loading: + loadingView + .onAppear(perform: self.loadDocument) + case let .error(message): VStack { Text("An error occurred") .font(.headline) - Text(errorMessage) + Text(message) } - } else { - loadingView - .onAppear(perform: self.loadDocument) + case let .document(doc): + DocumentView(document: doc, changeURL: navigator.changeURL) } } @@ -50,25 +50,39 @@ struct ContentView: View { private func loadDocument() { let url = navigator.currentURL task = try! GeminiDataTask(url: url, completion: { (response) in + self.task = nil switch response { case let .failure(error): - self.errorMessage = error.localizedDescription + state = .error(error.localizedDescription) case let .success(response): - guard let text = response.bodyText else { - self.errorMessage = "Response had no body text" - return + if response.status.isRedirect { + if let redirect = URL(string: response.meta) { + navigator.changeURL(redirect) + loadDocument() + } else { + state = .error("Invalid redirect URL: '\(response.meta)'") + } + } else if response.status.isSuccess, + let text = response.bodyText { + state = .document(GeminiParser.parse(text: text, baseURL: url)) + } else { + state = .error("Unknown error: \(response.header)") } - self.document = GeminiParser.parse(text: text, baseURL: url) } - self.task = nil }) task!.resume() } private func urlChanged(_ newValue: URL) { - self.task = nil - self.document = nil - self.errorMessage = nil + state = .loading + } +} + +extension ContentView { + enum ViewState { + case loading + case document(Document) + case error(String) } } diff --git a/GeminiProtocol/GeminiResponseHeader.swift b/GeminiProtocol/GeminiResponseHeader.swift index 4d4ccdf..7634525 100644 --- a/GeminiProtocol/GeminiResponseHeader.swift +++ b/GeminiProtocol/GeminiResponseHeader.swift @@ -35,15 +35,15 @@ public extension GeminiResponseHeader { case certificateNotValid = 62 // Status type helpers - var isInput: Bool { rawValue / 10 == 1 } - var isSuccess: Bool { rawValue / 10 == 2 } - var isRedirect: Bool { rawValue / 10 == 3 } - var isTemporaryFailure: Bool { rawValue / 10 == 4 } - var isPermanentFailure: Bool { rawValue / 10 == 5 } - var isClientCertificateRequired: Bool { rawValue / 10 == 6 } + public var isInput: Bool { rawValue / 10 == 1 } + public var isSuccess: Bool { rawValue / 10 == 2 } + public var isRedirect: Bool { rawValue / 10 == 3 } + public var isTemporaryFailure: Bool { rawValue / 10 == 4 } + public var isPermanentFailure: Bool { rawValue / 10 == 5 } + public var isClientCertificateRequired: Bool { rawValue / 10 == 6 } // Other helpers - var isFailure: Bool { isTemporaryFailure || isPermanentFailure } + public var isFailure: Bool { isTemporaryFailure || isPermanentFailure } } }