Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 0 additions & 59 deletions Sources/NextcloudKitUI/Modifiers/WebViewSheet.swift

This file was deleted.

28 changes: 22 additions & 6 deletions Sources/NextcloudKitUI/Views/Login/LoginFlowModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: 2026 Milen Pivchev
// SPDX-License-Identifier: GPL-3.0-or-later

import AuthenticationServices
#if os(iOS)
import CodeScanner
#endif
Expand Down Expand Up @@ -40,8 +41,8 @@ final class LoginFlowModel: QRCodeParsing, URLSanitizing {
/// State toggle for presenting the error alert.
var isPresentingAlert = false

/// State toggle for presenting the web view.
var isPresentingWebView = false
/// State toggle for presenting the login page in the system browser.
var isPresentingLoginPage = false

/// The login address acquired from the server through the login flow API.
var loginAddress: URL?
Expand Down Expand Up @@ -87,7 +88,7 @@ final class LoginFlowModel: QRCodeParsing, URLSanitizing {
startPolling(token: token, endpoint: endpoint)

loginAddress = login
isPresentingWebView = true
isPresentingLoginPage = true
} catch {
endLogin(error)
}
Expand Down Expand Up @@ -124,21 +125,36 @@ final class LoginFlowModel: QRCodeParsing, URLSanitizing {
}
}

func presentLoginPage(_ url: URL, using session: WebAuthenticationSession) async {
do {
_ = try await session.authenticate(using: url, callbackURLScheme: "nc", preferredBrowserSession: .ephemeral)
} catch {
// Already handled when the login completed or was cancelled.
guard isPresentingLoginPage else { return }

if let error = error as? ASWebAuthenticationSessionError, error.code == .canceledLogin {
cancel()
} else {
endLogin(error)
}
}
}

private func complete(host: URL, name: String, password: String) {
pollingTask = nil
isActive = false
isPresentingWebView = false
isPresentingLoginPage = false
addAccount(host, name, password)
}

///
/// Stop polling. Called when the web view is dismissed, regardless of the reason.
/// Stop polling. Called when the login page is closed, regardless of the reason.
///
func cancel() {
pollingTask?.cancel()
pollingTask = nil
isActive = false
isPresentingWebView = false
isPresentingLoginPage = false
}

private func endLogin(_ error: Error?) {
Expand Down
13 changes: 9 additions & 4 deletions Sources/NextcloudKitUI/Views/Login/ServerAddressView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: 2025 Iva Horn
// SPDX-License-Identifier: GPL-3.0-or-later

import AuthenticationServices
import SwiftUI

///
Expand All @@ -18,7 +19,6 @@
public struct ServerAddressView: View {
let brandImage: Image
var sharedAccounts: [SharedAccount]
let userAgent: String?

///
/// Create a new server address view.
Expand All @@ -27,21 +27,21 @@
/// - backgroundColor: The main theme color the view should use. Foreground color will be adapted automatically based on this.
/// - brandImage: The image to display on top of the server address view. Falls back to an SF Symbol placeholder in case of `nil`.
/// - sharedAccounts: Any shared accounts from the app group being available for selection and faster login.
/// - userAgent: An optional user agent string to override the one used by ``WKWebView``.
/// - userAgent: An optional user agent for server requests.
/// - addAccount: see ``AddAccountHandler``.
///
public init(backgroundColor: Binding<Color>, brandImage: Image, sharedAccounts: [SharedAccount], userAgent: String? = nil, addAccount: @escaping AddAccountHandler) {
self._backgroundColor = backgroundColor
self.brandImage = brandImage
self.sharedAccounts = sharedAccounts
self.userAgent = userAgent
self._model = State(initialValue: LoginFlowModel(userAgent: userAgent, addAccount: addAccount))
}

// MARK: - Environment

@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Environment(\.verticalSizeClass) var verticalSizeClass
@Environment(\.webAuthenticationSession) private var webAuthenticationSession

// MARK: - Bindings

Expand Down Expand Up @@ -161,7 +161,12 @@
.safeAreaPadding(.all)
}
.ignoresSafeArea()
.webSheet(initialURL: $model.loginAddress, isPresented: $model.isPresentingWebView, userAgent: userAgent, onDismiss: { model.cancel() })
.task(id: model.isPresentingLoginPage) {
// Clearing the flag cancels this task, which closes the browser sheet.
guard model.isPresentingLoginPage, let loginAddress = model.loginAddress else { return }

await model.presentLoginPage(loginAddress, using: webAuthenticationSession)
}
.alert(String(localized: "Login Failed", bundle: .module, comment: "Alert title"), isPresented: $model.isPresentingAlert) {
Button(role: .cancel) {
model.errorMessage = nil
Expand All @@ -176,7 +181,7 @@

#if DEBUG

// swiftlint:disable force_unwrapping

Check warning on line 184 in Sources/NextcloudKitUI/Views/Login/ServerAddressView.swift

View workflow job for this annotation

GitHub Actions / Lint

Blanket Disable Command Violation: Use 'next', 'this' or 'previous' instead to disable the 'force_unwrapping' rule once, or re-enable it as soon as possible` (blanket_disable_command)

#Preview("Without Shared Accounts") {
let backgroundColor: Binding<Color> = .constant(.accentColor)
Expand Down
34 changes: 10 additions & 24 deletions Sources/NextcloudKitUI/Views/Login/WebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,13 @@
/// All of the web view session data is not persistent.
///
public struct WebView: ViewRepresentable {
///
/// This must be a binding to support presentation inside sheets.
/// The way this view gets created and updated inside a sheet has a race condition with sheet presentation.
/// A sheet might be displayed before a passed in state variable might have been set.
///
@Binding var initialURL: URL?
/// Loaded once when the web view is created, so SwiftUI updates don't reload the page.
let initialURL: URL?

let userAgent: String?

public init(initialURL: Binding<URL?>, userAgent: String? = nil) {
self._initialURL = initialURL
public init(initialURL: URL?, userAgent: String? = nil) {
self.initialURL = initialURL
self.userAgent = userAgent
}

Expand All @@ -41,17 +37,11 @@
webView.customUserAgent = userAgent
}

return webView
}

func updateView(_ webView: WKWebView, context: Context) {

guard let initialURL = initialURL else {
return
if let initialURL {
webView.load(URLRequest(url: initialURL))
}

let request = URLRequest(url: initialURL)
webView.load(request)
return webView
}

// MARK: - macOS
Expand All @@ -62,9 +52,7 @@
makeView()
}

public func updateNSView(_ webView: WKWebView, context: Context) {
updateView(webView, context: context)
}
public func updateNSView(_ webView: WKWebView, context: Context) {}

// MARK: - iOS

Expand All @@ -74,14 +62,12 @@
makeView()
}

public func updateUIView(_ webView: WKWebView, context: Context) {
updateView(webView, context: context)
}
public func updateUIView(_ webView: WKWebView, context: Context) {}

#endif
}

#Preview {
// swiftlint:disable force_unwrapping

Check warning on line 71 in Sources/NextcloudKitUI/Views/Login/WebView.swift

View workflow job for this annotation

GitHub Actions / Lint

Blanket Disable Command Violation: Use 'next', 'this' or 'previous' instead to disable the 'force_unwrapping' rule once, or re-enable it as soon as possible` (blanket_disable_command)
WebView(initialURL: .constant(URL(string: "http://localhost:8080")!), userAgent: nil)
WebView(initialURL: URL(string: "http://localhost:8080")!, userAgent: nil)
}
Loading