diff --git a/OpenCloud.xcodeproj/project.pbxproj b/OpenCloud.xcodeproj/project.pbxproj index 617cb448..3513d8ba 100644 --- a/OpenCloud.xcodeproj/project.pbxproj +++ b/OpenCloud.xcodeproj/project.pbxproj @@ -135,6 +135,7 @@ 4CB8ADE322DF6BA700F1FEBC /* PHAsset+Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8ADE222DF6BA700F1FEBC /* PHAsset+Upload.swift */; }; 4CB8ADE622DF6C2B00F1FEBC /* CIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8ADE522DF6C2B00F1FEBC /* CIImage+Extensions.swift */; }; 4CC46D212284C677009E938F /* BookmarkInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC46D202284C677009E938F /* BookmarkInfoViewController.swift */; }; + 197CD026C3B84E7CA6514554 /* BookmarkConnectionSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B488C395DC4D4170BB72ECCA /* BookmarkConnectionSettingsViewController.swift */; }; 4CC4A21222FA20AD00AE7E2C /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC4A21122FA20AD00AE7E2C /* URL+Extensions.swift */; }; 4CC4A21922FB4F4C00AE7E2C /* MediaUploadQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC4A21822FB4F4C00AE7E2C /* MediaUploadQueue.swift */; }; 5B1E3A432D93F26A00D62A50 /* OCLicenseFreeProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B1E3A412D93F26A00D62A50 /* OCLicenseFreeProvider.m */; }; @@ -1167,6 +1168,7 @@ 4CB8ADE222DF6BA700F1FEBC /* PHAsset+Upload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PHAsset+Upload.swift"; sourceTree = ""; }; 4CB8ADE522DF6C2B00F1FEBC /* CIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CIImage+Extensions.swift"; sourceTree = ""; }; 4CC46D202284C677009E938F /* BookmarkInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkInfoViewController.swift; sourceTree = ""; }; + B488C395DC4D4170BB72ECCA /* BookmarkConnectionSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkConnectionSettingsViewController.swift; sourceTree = ""; }; 4CC4A21122FA20AD00AE7E2C /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = ""; }; 4CC4A21822FB4F4C00AE7E2C /* MediaUploadQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadQueue.swift; sourceTree = ""; }; 54199937F74A129BC74DEB0A /* Pods_OpenCloudScreenshotsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OpenCloudScreenshotsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -3580,6 +3582,7 @@ DC2323E12AA7C59900BFF393 /* Setup */, DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */, 4CC46D202284C677009E938F /* BookmarkInfoViewController.swift */, + B488C395DC4D4170BB72ECCA /* BookmarkConnectionSettingsViewController.swift */, ); path = Bookmarks; sourceTree = ""; @@ -4593,6 +4596,7 @@ 6E91F37E21ECA6FD009436D2 /* CopyAction.swift in Sources */, 4CC4A21922FB4F4C00AE7E2C /* MediaUploadQueue.swift in Sources */, 4CC46D212284C677009E938F /* BookmarkInfoViewController.swift in Sources */, + 197CD026C3B84E7CA6514554 /* BookmarkConnectionSettingsViewController.swift in Sources */, DCB6B1F5292CC46B00D27573 /* AccountController+ItemActions.swift in Sources */, 6E5FC172221590B000F60846 /* DisplayHostViewController.swift in Sources */, DCB796A72BCFB6EC00D6D759 /* CreateShortcutFileViewController.swift in Sources */, diff --git a/OpenCloud/Bookmarks/BookmarkConnectionSettingsViewController.swift b/OpenCloud/Bookmarks/BookmarkConnectionSettingsViewController.swift new file mode 100644 index 00000000..432c7c6a --- /dev/null +++ b/OpenCloud/Bookmarks/BookmarkConnectionSettingsViewController.swift @@ -0,0 +1,88 @@ +// +// BookmarkConnectionSettingsViewController.swift +// OpenCloud +// +// Copyright © 2026 OpenCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2026, OpenCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://opencloud.eu/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import OpenCloudSDK +import OpenCloudAppShared + +class BookmarkConnectionSettingsViewController: StaticTableViewController { + + private let bookmark: OCBookmark + + init(bookmark: OCBookmark) { + self.bookmark = bookmark + super.init(style: .insetGrouped) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.title = OCLocalizedString("Connection", nil) + + addSection(makeCustomHTTPHeaderSection()) + addSection(makeClientCertificatePlaceholderSection()) + } + + private func makeCustomHTTPHeaderSection() -> StaticTableViewSection { + let nameRow = StaticTableViewRow(textFieldWithAction: { [weak self] (_, sender, action) in + guard let self, let textField = sender as? UITextField, action == .changed else { return } + self.setUserInfo(textField.text, for: OCBookmarkUserInfoKey.customHTTPHeaderName) + }, placeholder: OCLocalizedString("Header name", nil), + value: (bookmark.userInfo[OCBookmarkUserInfoKey.customHTTPHeaderName] as? String) ?? "", + autocorrectionType: .no, + identifier: "custom-http-header-name") + + let valueRow = StaticTableViewRow(textFieldWithAction: { [weak self] (_, sender, action) in + guard let self, let textField = sender as? UITextField, action == .changed else { return } + self.setUserInfo(textField.text, for: OCBookmarkUserInfoKey.customHTTPHeaderValue) + }, placeholder: OCLocalizedString("Header value", nil), + value: (bookmark.userInfo[OCBookmarkUserInfoKey.customHTTPHeaderValue] as? String) ?? "", + autocorrectionType: .no, + identifier: "custom-http-header-value") + + return StaticTableViewSection(headerTitle: OCLocalizedString("Custom HTTP Header", nil), + footerTitle: OCLocalizedString("Attached to every request for this account. Useful for reverse-proxy authentication.", nil), + identifier: "section-custom-http-header", + rows: [nameRow, valueRow]) + } + + private func makeClientCertificatePlaceholderSection() -> StaticTableViewSection { + let placeholderRow = StaticTableViewRow(rowWithAction: nil, + title: OCLocalizedString("Import certificate (.p12)…", nil), + accessoryType: .disclosureIndicator, + identifier: "client-certificate-placeholder") + placeholderRow.enabled = false + placeholderRow.selectable = false + + return StaticTableViewSection(headerTitle: OCLocalizedString("Client Certificate", nil), + footerTitle: nil, + identifier: "section-client-certificate", + rows: [placeholderRow]) + } + + private func setUserInfo(_ value: String?, for key: OCBookmarkUserInfoKey) { + if let value, !value.isEmpty { + bookmark.userInfo[key] = value as NSString + } else { + bookmark.userInfo.removeObject(forKey: key) + } + } +} diff --git a/OpenCloud/Bookmarks/BookmarkViewController.swift b/OpenCloud/Bookmarks/BookmarkViewController.swift index 1a4ba6f0..b5cf3a32 100644 --- a/OpenCloud/Bookmarks/BookmarkViewController.swift +++ b/OpenCloud/Bookmarks/BookmarkViewController.swift @@ -46,6 +46,8 @@ class BookmarkViewController: StaticTableViewController { var deleteAuthDataButtonRow : StaticTableViewRow? var activeTextField: UITextField? + var connectionSettingsSection : StaticTableViewSection? + var showOAuthInfoHeader = false var showedOAuthInfoHeader : Bool = false var tokenHelpSection : StaticTableViewSection? @@ -257,6 +259,14 @@ class BookmarkViewController: StaticTableViewController { credentialsSection = StaticTableViewSection(headerTitle: OCLocalizedString("Credentials", nil), footerTitle: nil, identifier: "section-credentials", rows: [ usernameRow!, passwordRow! ]) + // Connection Settings section: custom HTTP header + (future) client certificate + let connectionSettingsRow = StaticTableViewRow(rowWithAction: { [weak self] (_, _) in + guard let self, let bookmark = self.bookmark else { return } + let connectionSettings = BookmarkConnectionSettingsViewController(bookmark: bookmark) + self.navigationController?.pushViewController(connectionSettings, animated: true) + }, title: OCLocalizedString("Connection…", nil), accessoryType: .disclosureIndicator, identifier: "row-connection-settings") + connectionSettingsSection = StaticTableViewSection(headerTitle: OCLocalizedString("Connection", nil), footerTitle: nil, identifier: "section-connection-settings", rows: [ connectionSettingsRow ]) + // Input focus tracking urlRow?.textField?.delegate = self passwordRow?.textField?.delegate = self @@ -946,6 +956,19 @@ class BookmarkViewController: StaticTableViewController { } } + // Connection Settings: always shown, placed immediately after Credentials (or after URL when Credentials is hidden) + if let connectionSettingsSection { + let anchorIndex = credentialsSection?.attached == true ? credentialsSection?.index : urlSection?.index + if connectionSettingsSection.attached == false { + if let anchorIndex { + self.insertSection(connectionSettingsSection, at: anchorIndex + 1, animated: animated) + } + } else if let currentIndex = connectionSettingsSection.index, let anchorIndex, currentIndex != anchorIndex + 1 { + self.removeSection(connectionSettingsSection, animated: animated) + self.insertSection(connectionSettingsSection, at: anchorIndex + 1, animated: animated) + } + } + if showOAuthInfoHeader { var authMethodName = "OAuth2" diff --git a/OpenCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterURLViewController.swift b/OpenCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterURLViewController.swift index 7b83f47a..5f04c188 100644 --- a/OpenCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterURLViewController.swift +++ b/OpenCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterURLViewController.swift @@ -36,11 +36,42 @@ class BookmarkSetupStepEnterURLViewController: BookmarkSetupStepViewController { focusTextFields = [ urlTextField! ] - contentView = urlTextField + let connectionButton = ThemeCSSButton(withSelectors: []) + connectionButton.setTitle(OCLocalizedString("Connection…", nil), for: .normal) + connectionButton.titleLabel?.font = .preferredFont(forTextStyle: .footnote) + connectionButton.contentHorizontalAlignment = .trailing + connectionButton.addAction(UIAction(handler: { [weak self] _ in + self?.openConnectionSettings() + }), for: .primaryActionTriggered) + + let stack = UIStackView(arrangedSubviews: [ urlTextField!, connectionButton ]) + stack.axis = .vertical + stack.alignment = .fill + stack.spacing = 6 + stack.translatesAutoresizingMaskIntoConstraints = false + + contentView = stack updateState() } + private func openConnectionSettings() { + guard let bookmark = setupViewController?.composer?.bookmark else { return } + let connectionSettings = BookmarkConnectionSettingsViewController(bookmark: bookmark) + + if let navigationController { + navigationController.pushViewController(connectionSettings, animated: true) + } else { + // First-run wizard: BookmarkSetupViewController is the root content view (no nav controller). + // Wrap and present modally with a Done button so the screen is reachable in this mode too. + connectionSettings.navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: UIAction(handler: { [weak connectionSettings] _ in + connectionSettings?.dismiss(animated: true) + })) + let wrapper = ThemeNavigationController(rootViewController: connectionSettings) + present(wrapper, animated: true) + } + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) urlTextField?.becomeFirstResponder() diff --git a/ios-sdk b/ios-sdk index 88197f34..7cad6a95 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 88197f34dcb8dc905111e642fe0d199a2b1fe035 +Subproject commit 7cad6a95a761c9c032f5831afdd29741d5c6bba9