Skip to content

Commit 7bd1cbd

Browse files
Merge branch 'customized-master' of https://github.com/nextmcloud/ios into customized-master
2 parents 00d96a2 + 5c1e50d commit 7bd1cbd

441 files changed

Lines changed: 21789 additions & 7393 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/nmc-custom-stables.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ on:
3636
- 4.9.5
3737
default: stable-6.6.0
3838
default: stable-6.6.3
39+
default: stable-6.6.3
40+
default: stable-6.6.0
3941

4042
jobs:
4143
backport-custom:

.github/workflows/xcode.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ env:
1919
PROJECT: Nextcloud.xcodeproj
2020
DESTINATION: platform=iOS Simulator,name=iPhone 16,OS=18.5
2121
SCHEME: Nextcloud
22+
XCODE_VERSION: '26.0.1'
2223
SERVER_BRANCH: stable30
2324
PHP_VERSION: 8.3
2425

@@ -37,7 +38,7 @@ jobs:
3738
- name: Setup Xcode
3839
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
3940
with:
40-
xcode-version: '26.0.1'
41+
xcode-version: ${{ env.XCODE_VERSION }}
4142

4243
- name: Run Xcode Build
4344
run: |
@@ -60,13 +61,16 @@ jobs:
6061
runs-on: macos-15
6162
needs: [build]
6263

63-
# Temporarily, project has no effective tests except UI tests which are unfeasible on virtualized GitHub runners (see #3291)
64-
# Previously: github.event.pull_request.draft == false
65-
if: false
66-
64+
if: github.event.pull_request.draft == false
65+
6766
steps:
6867
- uses: actions/checkout@v4
6968

69+
- name: Setup Xcode
70+
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
71+
with:
72+
xcode-version: ${{ env.XCODE_VERSION }}
73+
7074
- name: Prepare PHP ${{ env.PHP_VERSION }}
7175
uses: shivammathur/setup-php@v2
7276
with:

AGENTS.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: GPL-2.0-or-later
4+
-->
5+
6+
# AGENTS.md
7+
8+
You are an experienced engineer specialized in Swift and familiar with the platform-specific details of iOS.
9+
10+
## Your Role
11+
12+
- You implement features and fix bugs.
13+
- Your documentation and explanations are written for less experienced contributors to ease understanding and learning.
14+
- You work on an open source project and lowering the barrier for contributors is part of your work.
15+
16+
## Project Overview
17+
18+
The Nextcloud iOS Client is a tool to access and synchronize files from Nextcloud Server on your iPhone or iPad.
19+
Swift, UIKit and SwiftUI are the key technologies used for building the app on iOS.
20+
Beyond that, there is shared code in the form of the NextcloudKit library for use in macOS and iOS.
21+
Other platforms like Android are irrelevant for this project.
22+
23+
## Project Structure: AI Agent Handling Guidelines
24+
25+
| Directory | Description | Agent Action |
26+
|-----------------|-----------------------------------------------------|----------------------|
27+
| `.github` | GitHub CI workflows. | Try to add unit tests for new features, where applicable and makes sense. Do not overcomplicate unit tests. |
28+
| `iOSClient` | Main iOS client source code. ||
29+
| `iOSClient/SupportingFiles` | Translation files from Transifex. | Only add new strings in `en.lproj`. The rest you should ignore. |
30+
| `File Provider Extension` | Contains business logic for the iOS File Provider extension. ||
31+
| `File Provider Extension UI` | Contains UI logic for the iOS File Provider extension. ||
32+
| `Share` | Contains code for the iOS Share extension. ||
33+
| `Widget` | Contains code for iOS Widgets. ||
34+
| `WidgetDashboardIntentHandler` | Contains the intent handler for the dashboard widget. ||
35+
| `Notification Service Extension` | Contains the push notification service extension. ||
36+
| `Tests` | Contains unit and UI tests. ||
37+
38+
## General Guidance
39+
40+
Every new file needs to get a SPDX header in the first rows according to this template.
41+
The year in the first line must be replaced with the year when the file is created (for example, 2026 for files first added in 2026).
42+
The commenting signs need to be used depending on the file type.
43+
44+
```plaintext
45+
SPDX-FileCopyrightText: <YEAR> Nextcloud GmbH and Nextcloud contributors
46+
SPDX-License-Identifier: GPL-2.0-or-later
47+
```
48+
49+
Avoid creating source files that implement multiple types; instead, place each type in its own dedicated source file.
50+
51+
## Commit and Pull Request Guidelines
52+
53+
- **DCO sign-off (required)**: All commits must comply with the Developer Certificate of Origin (DCO) as described in `README.md` and include a `Signed-off-by: …` line in the commit message.
54+
- **Commits**: In addition to the DCO sign-off, follow the Conventional Commits format for the subject line where reasonable. Use `feat: ...`, `fix: ...`, or `refactor: ...` as appropriate in the commit message prefix.
55+
- Include a short summary of what changed. *Example:* `fix: prevent crash on empty todo title`.
56+
- **Pull Request**: When the agent creates a PR, it should include a description summarizing the changes and why they were made. If a GitHub issue exists, reference it (e.g., “Closes #123”). If there is any discrepancy between this section and the contribution guidance in `README.md`, the rules in `README.md` take precedence.
57+
58+
## iOS Specifics
59+
60+
The following details are important when working on the iOS client.
61+
62+
### Requirements
63+
64+
- Latest stable Xcode available is required to be installed in the development environment.
65+
66+
### Code Style
67+
68+
- When writing code in Swift, respect strict concurrency rules and Swift 6 compatibility.
69+
70+
### Tests
71+
72+
- When implementing new test suites, prefer Swift Testing over XCTest for implementation.
73+
- When implementing test cases using Swift Testing, do not prefix test method names with "test".
74+
- If the implementation of mock types is inevitable, implement them in dedicated source code files and in a generic way, so they can be reused across all tests in a test target.
75+
- If the implementation of an existing mock type does not fulfill the requirements introduced by new tests, prefer updating the existing type before implementing a mostly redundant alternative type.
76+
- Verify that all tests are passing and correct them if necessary.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//
2+
// ActionViewController.swift
3+
// Action Assistant
4+
//
5+
// Created by Marino Faggiana on 14/05/2026.
6+
// Copyright © 2026 Marino Faggiana. All rights reserved.
7+
//
8+
9+
import UIKit
10+
import NextcloudKit
11+
import UniformTypeIdentifiers
12+
13+
final class ActionViewController: UIViewController {
14+
override func viewDidLoad() {
15+
super.viewDidLoad()
16+
17+
view.isHidden = true
18+
view.alpha = 0
19+
view.backgroundColor = .clear
20+
preferredContentSize = .zero
21+
22+
Task {
23+
await handleAction()
24+
}
25+
}
26+
27+
private func handleAction() async {
28+
guard let text = await loadText() else {
29+
extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
30+
return
31+
}
32+
33+
NCAssistantSharedTextStore.save(text)
34+
openMainAppForAssistantSharedText()
35+
}
36+
37+
private func loadText() async -> String? {
38+
guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
39+
return nil
40+
}
41+
42+
for extensionItem in extensionItems {
43+
guard let attachments = extensionItem.attachments else {
44+
continue
45+
}
46+
47+
for provider in attachments {
48+
if provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
49+
return await loadText(from: provider, typeIdentifier: UTType.plainText.identifier)
50+
}
51+
52+
if provider.hasItemConformingToTypeIdentifier(UTType.utf8PlainText.identifier) {
53+
return await loadText(from: provider, typeIdentifier: UTType.utf8PlainText.identifier)
54+
}
55+
56+
if provider.hasItemConformingToTypeIdentifier(UTType.text.identifier) {
57+
return await loadText(from: provider, typeIdentifier: UTType.text.identifier)
58+
}
59+
}
60+
}
61+
62+
return nil
63+
}
64+
65+
private func loadText(from provider: NSItemProvider, typeIdentifier: String) async -> String? {
66+
await withCheckedContinuation { continuation in
67+
provider.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, _ in
68+
let text: String?
69+
70+
if let string = item as? String {
71+
text = string
72+
} else if let attributedString = item as? NSAttributedString {
73+
text = attributedString.string
74+
} else if let data = item as? Data {
75+
text = String(data: data, encoding: .utf8)
76+
} else if let url = item as? URL {
77+
text = try? String(contentsOf: url, encoding: .utf8)
78+
} else {
79+
text = nil
80+
}
81+
82+
guard let text, !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
83+
continuation.resume(returning: nil)
84+
return
85+
}
86+
87+
continuation.resume(returning: text)
88+
}
89+
}
90+
}
91+
92+
private func openMainAppForAssistantSharedText() {
93+
guard let url = URL(string: "nextcloud://assistant/shared-text") else {
94+
extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
95+
return
96+
}
97+
98+
openAssistantSharedTextURLThroughResponderChain(url)
99+
100+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
101+
self?.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
102+
}
103+
}
104+
105+
/// Opens the Assistant shared-text deep link from the Share extension.
106+
///
107+
/// Share extensions cannot use `UIApplication.shared` directly because it is not
108+
/// extension-safe. This method walks the responder chain until it finds the hidden
109+
/// `UIApplication` responder and invokes the modern `open(_:options:completionHandler:)`
110+
/// Objective-C selector dynamically.
111+
///
112+
/// This is intentionally isolated because it relies on Objective-C runtime dispatch.
113+
///
114+
/// - Parameter url: Deep link URL to open in the containing application.
115+
private func openAssistantSharedTextURLThroughResponderChain(_ url: URL) {
116+
let selector = NSSelectorFromString("openURL:options:completionHandler:")
117+
let applicationClass: AnyClass? = NSClassFromString("UIApplication")
118+
var responder: UIResponder? = self
119+
120+
while let currentResponder = responder {
121+
guard let applicationClass,
122+
currentResponder.isKind(of: applicationClass),
123+
currentResponder.responds(to: selector),
124+
let implementation = currentResponder.method(for: selector) else {
125+
responder = currentResponder.next
126+
continue
127+
}
128+
129+
typealias CompletionBlock = @convention(block) (Bool) -> Void
130+
typealias OpenURLFunction = @convention(c) (AnyObject, Selector, NSURL, NSDictionary, CompletionBlock?) -> Void
131+
132+
let openURL = unsafeBitCast(implementation, to: OpenURLFunction.self)
133+
134+
let completion: CompletionBlock = { success in
135+
if success {
136+
nkLog(debug: "Assistant shared text deep link performed through modern responder chain")
137+
} else {
138+
nkLog(error: "Assistant shared text deep link modern responder chain returned false")
139+
}
140+
}
141+
142+
openURL(currentResponder, selector, url as NSURL, NSDictionary(), completion)
143+
return
144+
}
145+
146+
nkLog(error: "Assistant shared text deep link failed because no UIApplication responder can open URL")
147+
}
148+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24765" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="ObA-dk-sSI">
3+
<device id="retina6_12" orientation="portrait" appearance="light"/>
4+
<dependencies>
5+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24743"/>
6+
</dependencies>
7+
<scenes>
8+
<!--Image-->
9+
<scene sceneID="7MM-of-jgj">
10+
<objects>
11+
<viewController title="Image" id="ObA-dk-sSI" customClass="ActionViewController" customModule="Action_Assistant" sceneMemberID="viewController">
12+
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
13+
<size key="freeformSize" width="320" height="528"/>
14+
</viewController>
15+
<placeholder placeholderIdentifier="IBFirstResponder" id="X47-rx-isc" userLabel="First Responder" sceneMemberID="firstResponder"/>
16+
</objects>
17+
<point key="canvasLocation" x="139" y="131"/>
18+
</scene>
19+
</scenes>
20+
</document>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "Senza titolo.png",
5+
"idiom" : "universal",
6+
"platform" : "ios",
7+
"size" : "1024x1024"
8+
},
9+
{
10+
"appearances" : [
11+
{
12+
"appearance" : "luminosity",
13+
"value" : "dark"
14+
}
15+
],
16+
"filename" : "Senza titolo 1.png",
17+
"idiom" : "universal",
18+
"platform" : "ios",
19+
"size" : "1024x1024"
20+
},
21+
{
22+
"appearances" : [
23+
{
24+
"appearance" : "luminosity",
25+
"value" : "tinted"
26+
}
27+
],
28+
"filename" : "Senza titolo 2.png",
29+
"idiom" : "universal",
30+
"platform" : "ios",
31+
"size" : "1024x1024"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
8.22 KB
Loading
8.22 KB
Loading
8.22 KB
Loading
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}

0 commit comments

Comments
 (0)