Skip to content
Open
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
4 changes: 4 additions & 0 deletions OpenCloud.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1167,6 +1168,7 @@
4CB8ADE222DF6BA700F1FEBC /* PHAsset+Upload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PHAsset+Upload.swift"; sourceTree = "<group>"; };
4CB8ADE522DF6C2B00F1FEBC /* CIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CIImage+Extensions.swift"; sourceTree = "<group>"; };
4CC46D202284C677009E938F /* BookmarkInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkInfoViewController.swift; sourceTree = "<group>"; };
B488C395DC4D4170BB72ECCA /* BookmarkConnectionSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkConnectionSettingsViewController.swift; sourceTree = "<group>"; };
4CC4A21122FA20AD00AE7E2C /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
4CC4A21822FB4F4C00AE7E2C /* MediaUploadQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadQueue.swift; sourceTree = "<group>"; };
54199937F74A129BC74DEB0A /* Pods_OpenCloudScreenshotsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OpenCloudScreenshotsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -3580,6 +3582,7 @@
DC2323E12AA7C59900BFF393 /* Setup */,
DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */,
4CC46D202284C677009E938F /* BookmarkInfoViewController.swift */,
B488C395DC4D4170BB72ECCA /* BookmarkConnectionSettingsViewController.swift */,
);
path = Bookmarks;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down
88 changes: 88 additions & 0 deletions OpenCloud/Bookmarks/BookmarkConnectionSettingsViewController.swift
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/gpl-3.0.en.html>.
*
*/

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)
}
}
}
23 changes: 23 additions & 0 deletions OpenCloud/Bookmarks/BookmarkViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading