diff --git a/.xcode-version b/.xcode-version index 6dfe8b129..adbc6d2b1 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -14.3.1 +15.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index a17eb41f4..0e3cc2f33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Table of Contents +* [Changelog for 12.1.0](#changelog-for-owncloud-ios-client-1210-2024-01-29) * [Changelog for 12.0.3](#changelog-for-owncloud-ios-client-1203-2023-08-31) * [Changelog for 12.0.2](#changelog-for-owncloud-ios-client-1202-2023-06-23) * [Changelog for 12.0.1](#changelog-for-owncloud-ios-client-1201-2023-06-15) @@ -21,6 +22,112 @@ * [Changelog for 11.5.1](#changelog-for-owncloud-ios-client-1151-2021-02-17) * [Changelog for 11.5.0](#changelog-for-owncloud-ios-client-1150-2021-02-10) * [Changelog for 11.4.5 versions and below](#changelog-for-1145-versions-and-below) +# Changelog for ownCloud iOS Client [12.1.0] (2024-01-29) +The following sections list the changes in ownCloud iOS Client 12.1.0 relevant to +ownCloud admins and users. + +[12.1.0]: https://github.com/owncloud/ios-app/compare/milestone/12.0.3...milestone/12.1.0 + +## Summary + +* Bugfix - Available offline badge: [#1128](https://github.com/owncloud/ios-app/issues/1128) +* Bugfix - File Provider fixes: [#1294](https://github.com/owncloud/ios-app/pull/1294) +* Bugfix - Open ownCloud Links in App: [#1295](https://github.com/owncloud/ios-app/issues/1295) +* Bugfix - Show message in File Provider if no account has been set up: [#1306](https://github.com/owncloud/ios-app/pull/1306) +* Bugfix - Disable Markup Edit Mode iOS 17: [#1309](https://github.com/owncloud/ios-app/issues/1309) +* Bugfix - Adopt log format: [#11224](https://github.com/owncloud/client/issues/11224) +* Change - New account wizard: [#1274](https://github.com/owncloud/ios-app/pull/1274) +* Change - Text recognition actions for images: [#1283](https://github.com/owncloud/ios-app/pull/1283) +* Change - File extension / suffix protection: [#1292](https://github.com/owncloud/ios-app/issues/1292) +* Change - Share Action Extension "Save to ownCloud": [#1293](https://github.com/owncloud/ios-app/issues/1293) +* Change - Link naming: [#1297](https://github.com/owncloud/ios-app/issues/1297) +* Change - Remove Extension Build Flag (Intents): [#6112](https://github.com/owncloud/enterprise/issues/6112) + +## Details + +* Bugfix - Available offline badge: [#1128](https://github.com/owncloud/ios-app/issues/1128) + + Available offline badge was there after making unavailable offline. + + https://github.com/owncloud/ios-app/issues/1128 + +* Bugfix - File Provider fixes: [#1294](https://github.com/owncloud/ios-app/pull/1294) + + This branch addresses found issues in the File Provider: - fixes unanswered + thumbnail requests (leading to infinite thumbnail responses) - fixes incorrect + error being returned in response to thumbnail requests + + https://github.com/owncloud/ios-app/pull/1294 + +* Bugfix - Open ownCloud Links in App: [#1295](https://github.com/owncloud/ios-app/issues/1295) + + Fixed existing feature intending to open files/folders in app, by clicking a + private link from outside. + + https://github.com/owncloud/ios-app/issues/1295 + +* Bugfix - Show message in File Provider if no account has been set up: [#1306](https://github.com/owncloud/ios-app/pull/1306) + + This PR makes the File Provider UI show a message if no account has been set up + yet, offering to open the app. Previously, the FP UI was briefly shown and then + dismissed. + + https://github.com/owncloud/ios-app/pull/1306 + +* Bugfix - Disable Markup Edit Mode iOS 17: [#1309](https://github.com/owncloud/ios-app/issues/1309) + + Fixed disabling edit mode in markup document view on iOS 17 + + https://github.com/owncloud/ios-app/issues/1309 + +* Bugfix - Adopt log format: [#11224](https://github.com/owncloud/client/issues/11224) + + Use common http log format for ownCloud clients. + + https://github.com/owncloud/client/issues/11224 + +* Change - New account wizard: [#1274](https://github.com/owncloud/ios-app/pull/1274) + + - reimplementation of the new account wizard - support for reordering accounts + in the sidebar via drag and drop - adds location breadcrumb dropdown in the + viewer + + https://github.com/owncloud/ios-app/pull/1274 + +* Change - Text recognition actions for images: [#1283](https://github.com/owncloud/ios-app/pull/1283) + + Adds VisonKit interactions to the image viewer, allowing to select and interact + with recognized content (f.ex. text) like in the Photos app. + + https://github.com/owncloud/ios-app/pull/1283 + +* Change - File extension / suffix protection: [#1292](https://github.com/owncloud/ios-app/issues/1292) + + Prevents users from removing or changing the suffix for new documents and + document scanner. + + https://github.com/owncloud/ios-app/issues/1292 + +* Change - Share Action Extension "Save to ownCloud": [#1293](https://github.com/owncloud/ios-app/issues/1293) + + Adds a share action extension "Save to ownCloud", which will open the share + sheet view. + + https://github.com/owncloud/ios-app/issues/1293 + +* Change - Link naming: [#1297](https://github.com/owncloud/ios-app/issues/1297) + + Adds a Name field for link shares, allowing to enter and edit the name of link + shares. + + https://github.com/owncloud/ios-app/issues/1297 + +* Change - Remove Extension Build Flag (Intents): [#6112](https://github.com/owncloud/enterprise/issues/6112) + + Adds a build flag to remove app extensions from a IPA build. + + https://github.com/owncloud/enterprise/issues/6112 + # Changelog for ownCloud iOS Client [12.0.3] (2023-08-31) The following sections list the changes in ownCloud iOS Client 12.0.3 relevant to ownCloud admins and users. diff --git a/changelog/12.1.0_2024-01-29/11224 b/changelog/12.1.0_2024-01-29/11224 new file mode 100644 index 000000000..8292539a8 --- /dev/null +++ b/changelog/12.1.0_2024-01-29/11224 @@ -0,0 +1,5 @@ +Bugfix: Adopt log format + +Use common http log format for ownCloud clients. + +https://github.com/owncloud/client/issues/11224 diff --git a/changelog/12.1.0_2024-01-29/1128 b/changelog/12.1.0_2024-01-29/1128 new file mode 100644 index 000000000..df8a31810 --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1128 @@ -0,0 +1,5 @@ +Bugfix: Available offline badge + +Available offline badge was there after making unavailable offline. + +https://github.com/owncloud/ios-app/issues/1128 diff --git a/changelog/12.1.0_2024-01-29/1274 b/changelog/12.1.0_2024-01-29/1274 new file mode 100644 index 000000000..3f0efe4ab --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1274 @@ -0,0 +1,7 @@ +Change: New account wizard + +- reimplementation of the new account wizard +- support for reordering accounts in the sidebar via drag and drop +- adds location breadcrumb dropdown in the viewer + +https://github.com/owncloud/ios-app/pull/1274 diff --git a/changelog/12.1.0_2024-01-29/1283 b/changelog/12.1.0_2024-01-29/1283 new file mode 100644 index 000000000..50bece0f9 --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1283 @@ -0,0 +1,5 @@ +Change: Text recognition actions for images + +Adds VisonKit interactions to the image viewer, allowing to select and interact with recognized content (f.ex. text) like in the Photos app. + +https://github.com/owncloud/ios-app/pull/1283 diff --git a/changelog/12.1.0_2024-01-29/1292 b/changelog/12.1.0_2024-01-29/1292 new file mode 100644 index 000000000..af21ca58a --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1292 @@ -0,0 +1,5 @@ +Change: File extension / suffix protection + +Prevents users from removing or changing the suffix for new documents and document scanner. + +https://github.com/owncloud/ios-app/issues/1292 diff --git a/changelog/12.1.0_2024-01-29/1293 b/changelog/12.1.0_2024-01-29/1293 new file mode 100644 index 000000000..70613b963 --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1293 @@ -0,0 +1,5 @@ +Change: Share Action Extension "Save to ownCloud" + +Adds a share action extension "Save to ownCloud", which will open the share sheet view. + +https://github.com/owncloud/ios-app/issues/1293 diff --git a/changelog/12.1.0_2024-01-29/1294 b/changelog/12.1.0_2024-01-29/1294 new file mode 100644 index 000000000..2c4a7b9fc --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1294 @@ -0,0 +1,7 @@ +Bugfix: File Provider fixes + +This branch addresses found issues in the File Provider: +- fixes unanswered thumbnail requests (leading to infinite thumbnail responses) +- fixes incorrect error being returned in response to thumbnail requests + +https://github.com/owncloud/ios-app/pull/1294 diff --git a/changelog/12.1.0_2024-01-29/1295 b/changelog/12.1.0_2024-01-29/1295 new file mode 100644 index 000000000..3d65670ac --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1295 @@ -0,0 +1,5 @@ +Bugfix: Open ownCloud Links in App + +Fixed existing feature intending to open files/folders in app, by clicking a private link from outside. + +https://github.com/owncloud/ios-app/issues/1295 diff --git a/changelog/12.1.0_2024-01-29/1297 b/changelog/12.1.0_2024-01-29/1297 new file mode 100644 index 000000000..ff9bee06c --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1297 @@ -0,0 +1,5 @@ +Change: Link naming + +Adds a Name field for link shares, allowing to enter and edit the name of link shares. + +https://github.com/owncloud/ios-app/issues/1297 diff --git a/changelog/12.1.0_2024-01-29/1306 b/changelog/12.1.0_2024-01-29/1306 new file mode 100644 index 000000000..693a42cef --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1306 @@ -0,0 +1,5 @@ +Bugfix: Show message in File Provider if no account has been set up + +This PR makes the File Provider UI show a message if no account has been set up yet, offering to open the app. Previously, the FP UI was briefly shown and then dismissed. + +https://github.com/owncloud/ios-app/pull/1306 diff --git a/changelog/12.1.0_2024-01-29/1309 b/changelog/12.1.0_2024-01-29/1309 new file mode 100644 index 000000000..0d751b7da --- /dev/null +++ b/changelog/12.1.0_2024-01-29/1309 @@ -0,0 +1,5 @@ +Bugfix: Disable Markup Edit Mode iOS 17 + +Fixed disabling edit mode in markup document view on iOS 17 + +https://github.com/owncloud/ios-app/issues/1309 diff --git a/changelog/12.1.0_2024-01-29/6112 b/changelog/12.1.0_2024-01-29/6112 new file mode 100644 index 000000000..5f20f4ef0 --- /dev/null +++ b/changelog/12.1.0_2024-01-29/6112 @@ -0,0 +1,5 @@ +Change: Remove Extension Build Flag (Intents) + +Adds a build flag to remove app extensions from a IPA build. + +https://github.com/owncloud/enterprise/issues/6112 diff --git a/doc/BUILD_CUSTOMIZATION.md b/doc/BUILD_CUSTOMIZATION.md index ce413fb34..524eb1335 100644 --- a/doc/BUILD_CUSTOMIZATION.md +++ b/doc/BUILD_CUSTOMIZATION.md @@ -36,3 +36,7 @@ Removes the following from the app: Removes the following from the app: - the `NSAppTransportSecurity` dictionary from the app's `Info.plist` - including the `NSAllowsArbitraryLoads` key that's needed to allow plain/unsecured HTTP connections +- +### `REMOVE_EXTENSION_INTENTS` + +Removes the Intents extension binary from the IPA after building the app with fastlane diff --git a/doc/CONFIGURATION.json b/doc/CONFIGURATION.json index 0cdec2176..c5c8f4bbd 100644 --- a/doc/CONFIGURATION.json +++ b/doc/CONFIGURATION.json @@ -1,43 +1,4 @@ [ - { - "autoExpansion" : "none", - "category" : "Bookmarks", - "categoryTag" : "bookmarks", - "classIdentifier" : "account-settings", - "className" : "ownCloud.AccountSettingsProvider", - "description" : "The default name for the creation of new bookmarks.", - "flatIdentifier" : "account-settings.default-name", - "key" : "default-name", - "label" : "account-settings.default-name", - "status" : "supported", - "type" : "string" - }, - { - "autoExpansion" : "none", - "category" : "Bookmarks", - "categoryTag" : "bookmarks", - "classIdentifier" : "account-settings", - "className" : "ownCloud.AccountSettingsProvider", - "description" : "The default URL for the creation of new bookmarks.", - "flatIdentifier" : "account-settings.default-url", - "key" : "default-url", - "label" : "account-settings.default-url", - "status" : "supported", - "type" : "string" - }, - { - "autoExpansion" : "none", - "category" : "Bookmarks", - "categoryTag" : "bookmarks", - "classIdentifier" : "account-settings", - "className" : "ownCloud.AccountSettingsProvider", - "description" : "Controls whether the server URL in the text field during the creation of new bookmarks can be changed.", - "flatIdentifier" : "account-settings.url-editable", - "key" : "url-editable", - "label" : "account-settings.url-editable", - "status" : "supported", - "type" : "bool" - }, { "autoExpansion" : "none", "category" : "Actions", @@ -766,6 +727,7 @@ "categoryTag" : "branding", "classIdentifier" : "branding", "className" : "Branding", + "defaultValue" : true, "description" : "Indicates if the user can change the server URL for the account.", "flatIdentifier" : "branding.profile-allow-url-configuration", "key" : "profile-allow-url-configuration", @@ -910,12 +872,34 @@ "categoryTag" : "branding", "classIdentifier" : "branding", "className" : "Branding", - "description" : "Array of dictionaries, each specifying a theme.", - "flatIdentifier" : "branding.theme-definitions", - "key" : "theme-definitions", - "label" : "branding.theme-definitions", + "description" : "Values to use in system-color-based themes for branded clients. Mutually exclusive with theme-definitions.", + "flatIdentifier" : "branding.theme-colors", + "key" : "theme-colors", + "label" : "Theme Colors", + "possibleKeys" : [ + { + "description" : "Color to use as tint/accent color for controls (in hex notation).", + "value" : "tint-color" + }, + { + "description" : "Color to use as background color for brand views (in hex notation).", + "value" : "branding-background-color" + }, + { + "description" : "The status bar style in the setup wizard, affecting the status bar text color. Can be either `default`, `black` or `white`.", + "value" : "setup-status-bar-style" + }, + { + "description" : "Color to fill file icons with (in hex notation).", + "value" : "file-icon-color" + }, + { + "description" : "Color to fill folder icons with (in hex notation).", + "value" : "folder-icon-color" + } + ], "status" : "advanced", - "type" : "dictionaryArray" + "type" : "dictionary" }, { "autoExpansion" : "none", @@ -923,12 +907,25 @@ "categoryTag" : "branding", "classIdentifier" : "branding", "className" : "Branding", - "description" : "Dictionary defining generic colors that can be used in the definitions.", - "flatIdentifier" : "branding.theme-generic-colors", - "key" : "theme-generic-colors", - "label" : "branding.theme-generic-colors", + "description" : "CSS records to add to the CSS space of system-color-based themes for branded clients. Mutually exclusive with theme-definitions.", + "flatIdentifier" : "branding.theme-css-records", + "key" : "theme-css-records", + "label" : "Theme CSS Records", "status" : "advanced", - "type" : "dictionary" + "type" : "stringArray" + }, + { + "autoExpansion" : "none", + "category" : "Branding", + "categoryTag" : "branding", + "classIdentifier" : "branding", + "className" : "Branding", + "description" : "Array of dictionaries, each specifying a theme.", + "flatIdentifier" : "branding.theme-definitions", + "key" : "theme-definitions", + "label" : "branding.theme-definitions", + "status" : "advanced", + "type" : "dictionaryArray" }, { "autoExpansion" : "none", @@ -1105,6 +1102,19 @@ "status" : "supported", "type" : "string" }, + { + "autoExpansion" : "none", + "category" : "Build", + "categoryTag" : "build", + "classIdentifier" : "build", + "className" : "BuildOptions", + "description" : "Sets a custom version number for the app.", + "flatIdentifier" : "build.version-number", + "key" : "version-number", + "label" : "build.version-number", + "status" : "supported", + "type" : "string" + }, { "autoExpansion" : "none", "category" : "Connection", @@ -1839,6 +1849,14 @@ "description" : "Extension with the identifier simple-apm.", "value" : "simple-apm" }, + { + "description" : "Extension with the identifier system.dark.", + "value" : "system.dark" + }, + { + "description" : "Extension with the identifier system.light.", + "value" : "system.light" + }, { "description" : "Extension with the identifier web-finger.", "value" : "web-finger" @@ -2307,6 +2325,20 @@ "status" : "advanced", "type" : "bool" }, + { + "autoExpansion" : "none", + "category" : "Passcode", + "categoryTag" : "passcode", + "classIdentifier" : "passcode", + "className" : "AppLockSettings", + "defaultValue" : false, + "description" : "Controls wether the user MUST establish a passcode upon app installation, if NO device passcode protection is set.", + "flatIdentifier" : "passcode.enforced-by-device", + "key" : "enforced-by-device", + "label" : "passcode.enforced-by-device", + "status" : "advanced", + "type" : "bool" + }, { "autoExpansion" : "none", "category" : "Passcode", diff --git a/doc/configuration.adoc b/doc/configuration.adoc index 22277debf..bb8f82d08 100644 --- a/doc/configuration.adoc +++ b/doc/configuration.adoc @@ -344,24 +344,6 @@ tag::bookmarks[] |Status -|account-settings.default-name -|string -| -|The default name for the creation of new bookmarks. -|supported `candidate` - -|account-settings.default-url -|string -| -|The default URL for the creation of new bookmarks. -|supported `candidate` - -|account-settings.url-editable -|bool -| -|Controls whether the server URL in the text field during the creation of new bookmarks can be changed. -|supported `candidate` - |bookmark.prepopulation |string | @@ -456,7 +438,7 @@ branding.can-edit-account + branding.profile-allow-url-configuration |bool -| +|`true` |Indicates if the user can change the server URL for the account. |advanced `candidate` @@ -540,16 +522,47 @@ branding.sidebar-links-title |Title for the sidebar links section. |advanced `candidate` -|branding.theme-definitions -|dictionaryArray +|**Theme Colors** + + + +branding.theme-colors +|dictionary | -|Array of dictionaries, each specifying a theme. +|Values to use in system-color-based themes for branded clients. Mutually exclusive with theme-definitions. +[cols="1,1"] +!=== +! Key +! Value +! `tint-color` +! Color to use as tint/accent color for controls (in hex notation). + +! `branding-background-color` +! Color to use as background color for brand views (in hex notation). + +! `setup-status-bar-style` +! The status bar style in the setup wizard, affecting the status bar text color. Can be either `default`, `black` or `white`. + +! `file-icon-color` +! Color to fill file icons with (in hex notation). + +! `folder-icon-color` +! Color to fill folder icons with (in hex notation). + +!=== + |advanced `candidate` -|branding.theme-generic-colors -|dictionary +|**Theme CSS Records** + + + +branding.theme-css-records +|stringArray +| +|CSS records to add to the CSS space of system-color-based themes for branded clients. Mutually exclusive with theme-definitions. +|advanced `candidate` + +|branding.theme-definitions +|dictionaryArray | -|Dictionary defining generic colors that can be used in the definitions. +|Array of dictionaries, each specifying a theme. |advanced `candidate` |**Documentation URL** + @@ -668,6 +681,12 @@ tag::build[] |Set a custom app group identifier via Branding.plist parameter. This value will be set by fastlane. Changes OCAppGroupIdentifier, OCKeychainAccessGroupIdentifier only. Fastlane does not need the provisioning profile and certificate with the given app group identifer. Needs resigning with the correct provisioning profile and certificate. This is needed, if a customer is using an own resigning script which does not handle setting the app group identifier. |supported `candidate` +|build.version-number +|string +| +|Sets a custom version number for the app. +|supported `candidate` + |=== end::build[] @@ -1137,6 +1156,12 @@ tag::extensions[] ! `simple-apm` ! Extension with the identifier simple-apm. +! `system.dark` +! Extension with the identifier system.dark. + +! `system.light` +! Extension with the identifier system.light. + ! `web-finger` ! Extension with the identifier web-finger. @@ -1493,6 +1518,12 @@ tag::passcode[] |Controls wether the user MUST establish a passcode upon app installation. |advanced `candidate` +|passcode.enforced-by-device +|bool +|`false` +|Controls wether the user MUST establish a passcode upon app installation, if NO device passcode protection is set. +|advanced `candidate` + |passcode.lockDelay |int | diff --git a/enterprise/register/registerOwncloudApp.rb b/enterprise/register/registerOwncloudApp.rb index 5babab647..2f3ace4bd 100755 --- a/enterprise/register/registerOwncloudApp.rb +++ b/enterprise/register/registerOwncloudApp.rb @@ -1,5 +1,14 @@ #!/usr/bin/env ruby + # Copyright (C) 2023, ownCloud GmbH. + # + # This code is covered by the GNU Public License Version 3. + # + # For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + # You should have received a copy of this license along with this program. If not, see . + + # Version 1.1.0 + require 'spaceship' class AppRegistration @@ -304,3 +313,4 @@ def prepareProfile(bundle_id, profileFilename, cert) register.prepareAppID("File Provider UI", "FileProviderUI.mobileprovision", groups, registrationType, "#{bundlePrefix}.ios-app.ownCloud-File-ProviderUI", cert) register.prepareAppID("Intent", "Intent.mobileprovision", groups, registrationType, "#{bundlePrefix}.ios-app.ownCloud-Intent", cert) register.prepareAppID("ShareExtension", "ShareExtension.mobileprovision", groups, registrationType,"#{bundlePrefix}.ios-app.ownCloud-Share-Extension", cert) +register.prepareAppID("ActionExtension", "ActionExtension.mobileprovision", groups, registrationType,"#{bundlePrefix}.ios-app.ownCloud-Action-Extension", cert) diff --git a/enterprise/resign/README.md b/enterprise/resign/README.md index b26f92855..378a77cfe 100644 --- a/enterprise/resign/README.md +++ b/enterprise/resign/README.md @@ -16,6 +16,8 @@ This script allows you to resign the ownCloud App IPA file with a different Appl - `com.yourcompany.ios-app.ownCloud-Share-Extension` + - `com.yourcompany.ios-app.ownCloud-Action-Extension` + 2. Generate one App Group: - `group.com.yourcompany.ios-app` @@ -51,6 +53,7 @@ Create a text file containing a list of line-break separated domain names (FQN) - `FileProviderUI.mobileprovision` - `Intent.mobileprovision` - `ShareExtension.mobileprovision` + - `ActionExtension.mobileprovision` 5. Execute the script diff --git a/enterprise/resign/resignInspector.sh b/enterprise/resign/resignInspector.sh index 53e8156aa..8edf6581b 100755 --- a/enterprise/resign/resignInspector.sh +++ b/enterprise/resign/resignInspector.sh @@ -7,7 +7,7 @@ # For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ # You should have received a copy of this license along with this program. If not, see . - VERSION="1.0.0" + VERSION="1.1.0" #Define output formats BOLD="$(tput bold)" @@ -72,7 +72,7 @@ fi APPPATH="$APPTEMP/Payload/ownCloud.app" fi - declare -a LOCATIONS=( "$APPPATH/" "$APPPATH/PlugIns/ownCloud File Provider.appex" "$APPPATH/PlugIns/ownCloud File Provider UI.appex" "$APPPATH/PlugIns/ownCloud Intents.appex" "$APPPATH/PlugIns/ownCloud Share Extension.appex" ); + declare -a LOCATIONS=( "$APPPATH/" "$APPPATH/PlugIns/ownCloud File Provider.appex" "$APPPATH/PlugIns/ownCloud File Provider UI.appex" "$APPPATH/PlugIns/ownCloud Intents.appex" "$APPPATH/PlugIns/ownCloud Share Extension.appex" "$APPPATH/PlugIns/ownCloud Action Extension.appex" ); echo "${SUCCESS}Checking entitlements…${NC}" echo "" diff --git a/enterprise/resign/resignOwncloudApp b/enterprise/resign/resignOwncloudApp index f2fa208e7..695fa9c67 100755 --- a/enterprise/resign/resignOwncloudApp +++ b/enterprise/resign/resignOwncloudApp @@ -7,7 +7,7 @@ # For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ # You should have received a copy of this license along with this program. If not, see . -VERSION="1.2.2" +VERSION="1.3.0" #Define output formats BOLD="$(tput bold)" @@ -64,11 +64,12 @@ FILEPROVIDER_MOBILEPROVISION="$PROVISIONING_DIR/FileProvider.mobileprovision" FILEPROVIDERUI_MOBILEPROVISION="$PROVISIONING_DIR/FileProviderUI.mobileprovision" INTENT_MOBILEPROVISION="$PROVISIONING_DIR/Intent.mobileprovision" SHAREEXTENSION_MOBILEPROVISION="$PROVISIONING_DIR/ShareExtension.mobileprovision" +ACTIONEXTENSION_MOBILEPROVISION="$PROVISIONING_DIR/ActionExtension.mobileprovision" ASSOCIATED_DOMAINS="domains.txt" -declare -a MOBILEPROVISIONS=( "$DISTRIBUTION_MOBILEPROVISION" "$FILEPROVIDER_MOBILEPROVISION" "$FILEPROVIDERUI_MOBILEPROVISION" "$INTENT_MOBILEPROVISION" "$SHAREEXTENSION_MOBILEPROVISION" ); -declare -a ENTITLEMENTS=( "ownCloud.entitlements" "ownCloud_File_Provider.entitlements" "ownCloud_File_Provider_UI.entitlements" "ownCloud_Intents.entitlements" "ownCloud_Share_Extension.entitlements" ); -declare -a LOCATIONS=( "$APPDIR" "$APPDIR/PlugIns/ownCloud File Provider.appex" "$APPDIR/PlugIns/ownCloud File Provider UI.appex" "$APPDIR/PlugIns/ownCloud Intents.appex" "$APPDIR/PlugIns/ownCloud Share Extension.appex" ); +declare -a MOBILEPROVISIONS=( "$DISTRIBUTION_MOBILEPROVISION" "$FILEPROVIDER_MOBILEPROVISION" "$FILEPROVIDERUI_MOBILEPROVISION" "$INTENT_MOBILEPROVISION" "$SHAREEXTENSION_MOBILEPROVISION" "$ACTIONEXTENSION_MOBILEPROVISION" ); +declare -a ENTITLEMENTS=( "ownCloud.entitlements" "ownCloud_File_Provider.entitlements" "ownCloud_File_Provider_UI.entitlements" "ownCloud_Intents.entitlements" "ownCloud_Share_Extension.entitlements" "ownCloud_Action_Extension.entitlements" ); +declare -a LOCATIONS=( "$APPDIR" "$APPDIR/PlugIns/ownCloud File Provider.appex" "$APPDIR/PlugIns/ownCloud File Provider UI.appex" "$APPDIR/PlugIns/ownCloud Intents.appex" "$APPDIR/PlugIns/ownCloud Share Extension.appex" "$APPDIR/PlugIns/ownCloud Action Extension.appex" ); declare -a BUNDLEIDS=(); # Abort script if script is running in a path which contains "Library", because this could cause resource fork problems diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 90cd7e1f1..0f7640287 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -39,7 +39,8 @@ platform :ios do "com.owncloud.ios-app.ownCloud-File-Provider" => "match AdHoc com.owncloud.ios-app.ownCloud-File-Provider", "com.owncloud.ios-app.ownCloud-File-ProviderUI" => "match AdHoc com.owncloud.ios-app.ownCloud-File-ProviderUI", "com.owncloud.ios-app.ownCloud-Intents" => "match AdHoc com.owncloud.ios-app.ownCloud-Intents", - "com.owncloud.ios-app.ownCloud-Share-Extension" => "match AdHoc com.owncloud.ios-app.ownCloud-Share-Extension" + "com.owncloud.ios-app.ownCloud-Share-Extension" => "match AdHoc com.owncloud.ios-app.ownCloud-Share-Extension", + "com.owncloud.ios-app.ownCloud-Action-Extension" => "match AdHoc com.owncloud.ios-app.ownCloud-Action-Extension" #Add more Provisioning Profiles when extensions are added } } @@ -180,13 +181,13 @@ platform :ios do prepare_metadata( create_release_notes: options[:create_release_notes], create_screenshots: options[:create_screenshots], - app_name: "ownCloud", + app_name: "ownCloud EMM", metadata_path: 'fastlane/metadata-emm/en-US/release_notes.txt' ) puts("Build EMM iOS App…") # Moving all EMM resources to the correct place - sh "cp -R ../ownCloud/Resources/Theming/com.owncloud.ios-app.emm/* ../ownCloud/Resources/Theming/" + sh "cp -R ../ownCloud/Resources/Theming/com.owncloud.ios-app.emm/*.png ../ownCloud/Resources/Theming/branding-assets/" # Build App owncloud_emm_build() @@ -222,7 +223,8 @@ platform :ios do puts("Build ownCloud Online iOS App…") # Moving all ownCloud.online resources to the correct place - sh "cp -R ../ownCloud/Resources/Theming/online.owncloud.ios-app/* ../ownCloud/Resources/Theming/" + sh "cp -R ../ownCloud/Resources/Theming/online.owncloud.ios-app/*.png ../ownCloud/Resources/Theming/branding-assets/" + sh "cp ../ownCloud/Resources/Theming/online.owncloud.ios-app/Branding.plist ../ownCloud/Resources/Theming/" # Build App owncloud_online_build() @@ -258,6 +260,8 @@ platform :ios do ENTERPRISE_INTENT_PROFILE: "match AppStore com.owncloud.ios-app.ownCloud-Intents", ENTERPRISE_SHARE_EXTENSION_ID: "com.owncloud.ios-app.ownCloud-Share-Extension", ENTERPRISE_SHARE_EXTENSION_PROFILE: "match AppStore com.owncloud.ios-app.ownCloud-Share-Extension", + ENTERPRISE_ACTION_EXTENSION_ID: "com.owncloud.ios-app.ownCloud-Action-Extension", + ENTERPRISE_ACTION_EXTENSION_PROFILE: "match AppStore com.owncloud.ios-app.ownCloud-Action-Extension", ENTERPRISE_APP_FW_ID: "com.owncloud.ownCloudApp", ENTERPRISE_TEAM: "4AP2STM4H5", ENTERPRISE_IDENTITY: "Apple Distribution: ownCloud GmbH (4AP2STM4H5)", @@ -283,6 +287,8 @@ platform :ios do ENTERPRISE_INTENT_PROFILE: "match AppStore com.owncloud.ios-app.emm.ownCloud-Intents", ENTERPRISE_SHARE_EXTENSION_ID: "com.owncloud.ios-app.emm.ownCloud-Share-Extension", ENTERPRISE_SHARE_EXTENSION_PROFILE: "match AppStore com.owncloud.ios-app.emm.ownCloud-Share-Extension", + ENTERPRISE_ACTION_EXTENSION_ID: "com.owncloud.ios-app.emm.ownCloud-Action-Extension", + ENTERPRISE_ACTION_EXTENSION_PROFILE: "match AppStore com.owncloud.ios-app.emm.ownCloud-Action-Extension", ENTERPRISE_APP_FW_ID: "com.owncloud.ownCloudApp.emm", ENTERPRISE_TEAM: "4AP2STM4H5", ENTERPRISE_IDENTITY: "Apple Distribution: ownCloud GmbH (4AP2STM4H5)", @@ -290,7 +296,7 @@ platform :ios do EXPORT_METHOD: "app-store", CONFIGURATION: "Release", BETA_APP_ICON: false, - APP_NAME: "ownCloud", + APP_NAME: "ownCloud EMM", URL_SCHEME: "owncloud-emm", IPA_NAME: "ownCloud-emm" ) @@ -308,6 +314,8 @@ lane :owncloud_online_build do ENTERPRISE_INTENT_PROFILE: "ownCloud online AppStore Intents", ENTERPRISE_SHARE_EXTENSION_ID: "online.owncloud.ios-app.ShareExtApp", ENTERPRISE_SHARE_EXTENSION_PROFILE: "ownCloud online AppStore Share Ext", + ENTERPRISE_ACTION_EXTENSION_ID: "online.owncloud.ios-app.Action-Extension", + ENTERPRISE_ACTION_EXTENSION_PROFILE: "ownCloud online AppStore Action Extension", ENTERPRISE_APP_FW_ID: "online.owncloud.ios-app.ownCloudApp", ENTERPRISE_TEAM: "4AP2STM4H5", ENTERPRISE_IDENTITY: "Apple Distribution: ownCloud GmbH", @@ -333,6 +341,8 @@ end ENTERPRISE_INTENT_PROFILE: "Adhoc ownCloud iOS App Intents", ENTERPRISE_SHARE_EXTENSION_ID: "com.owncloud.ios-app.ownCloud-Share-Extension", ENTERPRISE_SHARE_EXTENSION_PROFILE: "Adhoc ownCloud iOS App Share Extension", + ENTERPRISE_ACTION_EXTENSION_ID: "com.owncloud.ios-app.ownCloud-Action-Extension", + ENTERPRISE_ACTION_EXTENSION_PROFILE: "Adhoc ownCloud iOS App Action Extension", ENTERPRISE_APP_FW_ID: "com.owncloud.ownCloudApp", ENTERPRISE_TEAM: "4AP2STM4H5", ENTERPRISE_IDENTITY: "Apple Distribution: ownCloud GmbH", @@ -356,6 +366,8 @@ end ENTERPRISE_INTENT_PROFILE: "match AppStore com.owncloud.ios-app.ownCloud-Intents", ENTERPRISE_SHARE_EXTENSION_ID: "com.owncloud.ios-app.ownCloud-Share-Extension", ENTERPRISE_SHARE_EXTENSION_PROFILE: "match AppStore com.owncloud.ios-app.ownCloud-Share-Extension", + ENTERPRISE_ACTION_EXTENSION_ID: "com.owncloud.ios-app.ownCloud-Action-Extension", + ENTERPRISE_ACTION_EXTENSION_PROFILE: "match AppStore com.owncloud.ios-app.ownCloud-Action-Extension", ENTERPRISE_APP_FW_ID: "com.owncloud.ownCloudApp", ENTERPRISE_TEAM: "4AP2STM4H5", ENTERPRISE_IDENTITY: "Apple Distribution: ownCloud GmbH (4AP2STM4H5)", @@ -389,6 +401,8 @@ end ENTERPRISE_INTENT_PROFILE: "Adhoc ownCloud iOS App Intents", ENTERPRISE_SHARE_EXTENSION_ID: "com.owncloud.ios-app.ownCloud-Share-Extension", ENTERPRISE_SHARE_EXTENSION_PROFILE: "Adhoc ownCloud iOS App Share Extension", + ENTERPRISE_ACTION_EXTENSION_ID: "com.owncloud.ios-app.ownCloud-Action-Extension", + ENTERPRISE_ACTION_EXTENSION_PROFILE: "Adhoc ownCloud iOS App Action Extension", ENTERPRISE_APP_FW_ID: "com.owncloud.ownCloudApp", ENTERPRISE_TEAM: "4AP2STM4H5", ENTERPRISE_IDENTITY: "iPhone Distribution: ownCloud GmbH", @@ -402,13 +416,18 @@ end end lane :generate_appicon do - iconPath = "ownCloud/Resources/Theming/branding-icon.png" - if File.exist?("../" + iconPath) + + iconPath = "ownCloud/Resources/Theming/branding-assets/" + iconName = "branding-icon.png" + outputIconPath = "../ownCloud/Resources/Assets.xcassets/" + outputIconName = "AppIcon.appiconset" + if File.exist?("../" + iconPath + iconName) + sh("rm -rf " + outputIconPath + outputIconName + "/*") appicon( - appicon_image_file: iconPath, + appicon_image_file: iconPath + iconName, appicon_devices: [:ipad, :iphone, :ios_marketing], appicon_path: "ownCloud/Resources/Assets.xcassets/", - appicon_name: "AppIcon.appiconset" + appicon_name: outputIconName ) end end @@ -426,6 +445,8 @@ end ENTERPRISE_INTENT_PROFILE = values[:ENTERPRISE_INTENT_PROFILE] ENTERPRISE_SHARE_EXTENSION_ID = values[:ENTERPRISE_SHARE_EXTENSION_ID] ENTERPRISE_SHARE_EXTENSION_PROFILE = values[:ENTERPRISE_SHARE_EXTENSION_PROFILE] + ENTERPRISE_ACTION_EXTENSION_ID = values[:ENTERPRISE_ACTION_EXTENSION_ID] + ENTERPRISE_ACTION_EXTENSION_PROFILE = values[:ENTERPRISE_ACTION_EXTENSION_PROFILE] ENTERPRISE_APP_FW_ID = values[:ENTERPRISE_APP_FW_ID] ENTERPRISE_APP_SHARED_ID = "com.owncloud.ownCloudAppShared" ENTERPRISE_TEAM = values[:ENTERPRISE_TEAM] @@ -548,7 +569,7 @@ end # Check, if Branding.plist file exists and handle custom set values if File.exist?("../" + themePath) # Check for custom app version number - tmpCustomAppVersionNumber = get_info_plist_value(path: themePath, key: "app.version-number") + tmpCustomAppVersionNumber = get_info_plist_value(path: themePath, key: "build.version-number") if tmpCustomAppVersionNumber && !tmpCustomAppVersionNumber.empty? customAppVersionNumber = tmpCustomAppVersionNumber @@ -557,6 +578,7 @@ end set_info_plist_value(path: "ownCloud File Provider/Info.plist", key: "CFBundleShortVersionString", value: customAppVersionNumber) set_info_plist_value(path: "ownCloud File Provider UI/Info.plist", key: "CFBundleShortVersionString", value: customAppVersionNumber) set_info_plist_value(path: "ownCloud Share Extension/Info.plist", key: "CFBundleShortVersionString", value: customAppVersionNumber) + set_info_plist_value(path: "ownCloud Action Extension/Info.plist", key: "CFBundleShortVersionString", value: customAppVersionNumber) set_info_plist_value(path: "ownCloud Intents/Info.plist", key: "CFBundleShortVersionString", value: customAppVersionNumber) end @@ -580,6 +602,7 @@ end set_info_plist_value(path: "ownCloud File Provider/Info.plist", key: "CFBundleVersion", value: BUILD_NUMBER) set_info_plist_value(path: "ownCloud File Provider UI/Info.plist", key: "CFBundleVersion", value: BUILD_NUMBER) set_info_plist_value(path: "ownCloud Share Extension/Info.plist", key: "CFBundleVersion", value: BUILD_NUMBER) + set_info_plist_value(path: "ownCloud Action Extension/Info.plist", key: "CFBundleVersion", value: BUILD_NUMBER) set_info_plist_value(path: "ownCloud Intents/Info.plist", key: "CFBundleVersion", value: BUILD_NUMBER) end @@ -601,6 +624,12 @@ end set_info_plist_value(path: "ownCloud Share Extension/Info.plist", key: "CFBundleDisplayName", value: "Share to " + appName) set_info_plist_value(path: "ownCloud Share Extension/Info.plist", key: "CFBundleName", value: appName) + set_info_plist_value(path: "ownCloud Action Extension/Info.plist", key: "CFBundleDisplayName", value: "Save to " + appName) + set_info_plist_value(path: "ownCloud Action Extension/Info.plist", key: "CFBundleName", value: appName) + + sh("sed -i '' '/\"CFBundleDisplayName\" =/s/ownCloud/#{appName}/' '../ownCloud Action Extension/en.lproj/InfoPlist.strings'") + sh("sed -i '' '/\"CFBundleDisplayName\" =/s/ownCloud/#{appName}/' '../ownCloud Action Extension/de.lproj/InfoPlist.strings'") + update_app_identifier( xcodeproj: "ownCloud.xcodeproj", plist_path: "ownCloud/Resources/Info.plist", @@ -637,6 +666,12 @@ end app_identifier: ENTERPRISE_SHARE_EXTENSION_ID ) + update_app_identifier( + xcodeproj: "ownCloud.xcodeproj", + plist_path: "ownCloud Action Extension/Info.plist", + app_identifier: ENTERPRISE_ACTION_EXTENSION_ID + ) + update_app_group_identifiers( entitlements_file: "ownCloud/ownCloud.entitlements", app_group_identifiers: [APP_GROUP_IDENTIFIERS] @@ -682,6 +717,15 @@ end identifiers: [ENTERPRISE_TEAM + "." + APP_GROUP_IDENTIFIERS] ) + update_app_group_identifiers( + entitlements_file: "ownCloud Action Extension/ownCloud Action Extension.entitlements", + app_group_identifiers: [APP_GROUP_IDENTIFIERS] + ) + update_keychain_access_groups( + entitlements_file: "ownCloud Action Extension/ownCloud Action Extension.entitlements", + identifiers: [ENTERPRISE_TEAM + "." + APP_GROUP_IDENTIFIERS] + ) + set_info_plist_value(path: "ownCloud File Provider/Info.plist", key: "OCAppGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) set_info_plist_value(path: "ownCloud File Provider/Info.plist", key: "OCKeychainAccessGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) set_info_plist_value(path: "ownCloud File Provider/Info.plist", key: "NSExtension", subkey: "NSExtensionFileProviderDocumentGroup", value: APP_GROUP_IDENTIFIERS) @@ -691,6 +735,8 @@ end set_info_plist_value(path: "ownCloud Intents/Info.plist", key: "OCKeychainAccessGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) set_info_plist_value(path: "ownCloud Share Extension/Info.plist", key: "OCAppGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) set_info_plist_value(path: "ownCloud Share Extension/Info.plist", key: "OCKeychainAccessGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) + set_info_plist_value(path: "ownCloud Action Extension/Info.plist", key: "OCAppGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) + set_info_plist_value(path: "ownCloud Action Extension/Info.plist", key: "OCKeychainAccessGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) set_info_plist_value(path: "ownCloud/Resources/Info.plist", key: "OCAppGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) set_info_plist_value(path: "ownCloud/Resources/Info.plist", key: "OCKeychainAccessGroupIdentifier", value: OC_APP_GROUP_IDENTIFIERS) @@ -753,6 +799,16 @@ end targets: ["ownCloud Share Extension"] ) + automatic_code_signing( + path: "ownCloud.xcodeproj", + use_automatic_signing: false, + team_id: ENTERPRISE_TEAM, + code_sign_identity: ENTERPRISE_IDENTITY, + profile_name: ENTERPRISE_ACTION_EXTENSION_PROFILE, + bundle_identifier: ENTERPRISE_ACTION_EXTENSION_ID, + targets: ["ownCloud Action Extension"] + ) + automatic_code_signing( path: "ownCloud.xcodeproj", use_automatic_signing: false, @@ -820,9 +876,17 @@ end ENTERPRISE_APP_ID => ENTERPRISE_APP_PROFILE, ENTERPRISE_FP_ID => ENTERPRISE_FP_PROFILE, ENTERPRISE_INTENT_ID => ENTERPRISE_INTENT_PROFILE, - ENTERPRISE_SHARE_EXTENSION_ID => ENTERPRISE_SHARE_EXTENSION_PROFILE + ENTERPRISE_SHARE_EXTENSION_ID => ENTERPRISE_SHARE_EXTENSION_PROFILE, + ENTERPRISE_ACTION_EXTENSION_ID => ENTERPRISE_ACTION_EXTENSION_PROFILE } } ) + + + + if appBuildFlags.include? "REMOVE_EXTENSION_INTENTS" + sh "../removeExtension.sh \"../" + ipaName.gsub("/", "_") + "\" \"ownCloud Intents\"" + end + end end diff --git a/fastlane/metadata-emm/en-US/release_notes.txt b/fastlane/metadata-emm/en-US/release_notes.txt index e9ca129c0..b8958eb2e 100644 --- a/fastlane/metadata-emm/en-US/release_notes.txt +++ b/fastlane/metadata-emm/en-US/release_notes.txt @@ -1,3 +1,32 @@ -• Bug Fixes -Fixed File Provider, App Provider and layout issues. +• New account wizard: +Introducing a new, intuitive account wizard user interface for a fast onboarding experience. +• Reordering accounts: +Now you can easily reorder accounts in the sidebar by simply dragging and dropping them. + +• Location breadcrumb: +We have added a location breadcrumb dropdown in the file viewer, making it easier to navigate through your files. + +• Text recognition: +You can now perform text recognition actions on images. + +• File extension / suffix protection: +To prevent accidental removal or modification of the suffix for new documents and document scanner files, we have implemented file extension protection. + +• Share Action "Save to ownCloud": +We have added a new action called "Save to ownCloud" to the share sheet, allowing you to easily save files to your ownCloud account. + +• Link naming: +You can now enter and edit the name of link shares. + +• Sort by Last Used: +Introducing a new sorting method - “Last Used” - which allows you to sort items based on the most recently accessed ones. + +• Markup Edit Mode on iOS 17: +We have fixed the issue that was disabling the edit mode in the markup document view on iOS 17. + +• Open ownCloud Links in App: +We have resolved the issue with opening private links from third-party apps. + +• Bug Fixes: +We have addressed various issues with the File Provider and the app itself. \ No newline at end of file diff --git a/fastlane/metadata-owncloud-online/en-US/release_notes.txt b/fastlane/metadata-owncloud-online/en-US/release_notes.txt index e9ca129c0..b8958eb2e 100644 --- a/fastlane/metadata-owncloud-online/en-US/release_notes.txt +++ b/fastlane/metadata-owncloud-online/en-US/release_notes.txt @@ -1,3 +1,32 @@ -• Bug Fixes -Fixed File Provider, App Provider and layout issues. +• New account wizard: +Introducing a new, intuitive account wizard user interface for a fast onboarding experience. +• Reordering accounts: +Now you can easily reorder accounts in the sidebar by simply dragging and dropping them. + +• Location breadcrumb: +We have added a location breadcrumb dropdown in the file viewer, making it easier to navigate through your files. + +• Text recognition: +You can now perform text recognition actions on images. + +• File extension / suffix protection: +To prevent accidental removal or modification of the suffix for new documents and document scanner files, we have implemented file extension protection. + +• Share Action "Save to ownCloud": +We have added a new action called "Save to ownCloud" to the share sheet, allowing you to easily save files to your ownCloud account. + +• Link naming: +You can now enter and edit the name of link shares. + +• Sort by Last Used: +Introducing a new sorting method - “Last Used” - which allows you to sort items based on the most recently accessed ones. + +• Markup Edit Mode on iOS 17: +We have fixed the issue that was disabling the edit mode in the markup document view on iOS 17. + +• Open ownCloud Links in App: +We have resolved the issue with opening private links from third-party apps. + +• Bug Fixes: +We have addressed various issues with the File Provider and the app itself. \ No newline at end of file diff --git a/fastlane/metadata/en-US/release_notes.txt b/fastlane/metadata/en-US/release_notes.txt index e9ca129c0..b8958eb2e 100644 --- a/fastlane/metadata/en-US/release_notes.txt +++ b/fastlane/metadata/en-US/release_notes.txt @@ -1,3 +1,32 @@ -• Bug Fixes -Fixed File Provider, App Provider and layout issues. +• New account wizard: +Introducing a new, intuitive account wizard user interface for a fast onboarding experience. +• Reordering accounts: +Now you can easily reorder accounts in the sidebar by simply dragging and dropping them. + +• Location breadcrumb: +We have added a location breadcrumb dropdown in the file viewer, making it easier to navigate through your files. + +• Text recognition: +You can now perform text recognition actions on images. + +• File extension / suffix protection: +To prevent accidental removal or modification of the suffix for new documents and document scanner files, we have implemented file extension protection. + +• Share Action "Save to ownCloud": +We have added a new action called "Save to ownCloud" to the share sheet, allowing you to easily save files to your ownCloud account. + +• Link naming: +You can now enter and edit the name of link shares. + +• Sort by Last Used: +Introducing a new sorting method - “Last Used” - which allows you to sort items based on the most recently accessed ones. + +• Markup Edit Mode on iOS 17: +We have fixed the issue that was disabling the edit mode in the markup document view on iOS 17. + +• Open ownCloud Links in App: +We have resolved the issue with opening private links from third-party apps. + +• Bug Fixes: +We have addressed various issues with the File Provider and the app itself. \ No newline at end of file diff --git a/img/filetypes-tvg/application-pdf.tvg b/img/filetypes-tvg/application-pdf.tvg index d1c1c517b..532446906 100644 --- a/img/filetypes-tvg/application-pdf.tvg +++ b/img/filetypes-tvg/application-pdf.tvg @@ -1 +1 @@ -{"defaults":{},"image":"\n\n \n<\/svg>\n","viewBox":"{{0, 0}, {16, 16}}"} \ No newline at end of file +{"defaults":{"pdfFileFillColor":"#dc5047"},"image":"\n\n \n<\/svg>\n","viewBox":"{{0, 0}, {16, 16}}"} \ No newline at end of file diff --git a/img/filetypes-tvg/x-office-document.tvg b/img/filetypes-tvg/x-office-document.tvg index e124f34a9..f52918311 100644 --- a/img/filetypes-tvg/x-office-document.tvg +++ b/img/filetypes-tvg/x-office-document.tvg @@ -1 +1 @@ -{"defaults":{},"image":"\n\n \n<\/svg>\n","viewBox":"{{0, 0}, {16, 16}}"} \ No newline at end of file +{"defaults":{"documentFileFillColor":"#49abea"},"image":"\n\n \n<\/svg>\n","viewBox":"{{0, 0}, {16, 16}}"} diff --git a/img/filetypes-tvg/x-office-presentation.tvg b/img/filetypes-tvg/x-office-presentation.tvg index c5efb948a..49e11c76a 100644 --- a/img/filetypes-tvg/x-office-presentation.tvg +++ b/img/filetypes-tvg/x-office-presentation.tvg @@ -1 +1 @@ -{"defaults":{},"image":"\n\n \n<\/svg>\n","viewBox":"{{0, 0}, {16, 16}}"} \ No newline at end of file +{"image":"\n\n \n<\/svg>\n","defaults":{"presentationFileFillColor":"#f0965f"},"viewBox":"{{0, 0}, {16, 16}}"} \ No newline at end of file diff --git a/img/filetypes-tvg/x-office-spreadsheet.tvg b/img/filetypes-tvg/x-office-spreadsheet.tvg index bb8d95818..eed0baf30 100644 --- a/img/filetypes-tvg/x-office-spreadsheet.tvg +++ b/img/filetypes-tvg/x-office-spreadsheet.tvg @@ -1 +1 @@ -{"defaults":{},"image":"\n\n \n<\/svg>\n","viewBox":"{{0, 0}, {16, 16}}"} \ No newline at end of file +{"image":"\n\n \n<\/svg>\n","defaults":{"spreadsheetFileFillColor":"#9abd4e"},"viewBox":"{{0, 0}, {16, 16}}"} \ No newline at end of file diff --git a/img/filetypes.json b/img/filetypes.json index 1a9ffc836..18c05e411 100644 --- a/img/filetypes.json +++ b/img/filetypes.json @@ -13,6 +13,30 @@ "replace" : "#969696", "variable" : "fileFillColor" }, + "fill=\"#49abea\"" : { + "description" : "Fill color used in office document", + + "replace" : "#49abea", + "variable" : "documentFileFillColor" + }, + "fill=\"#f0965f\"" : { + "description" : "Fill color used in presentation document", + + "replace" : "#f0965f", + "variable" : "presentationFileFillColor" + }, + "fill=\"#9abd4e\"" : { + "description" : "Fill color used in spreadsheet document", + + "replace" : "#9abd4e", + "variable" : "spreadsheetFileFillColor" + }, + "fill=\"#dc5047\"" : { + "description" : "Fill color used in PDF document", + + "replace" : "#dc5047", + "variable" : "pdfFileFillColor" + }, "fill=\"#1D293B\"" : { "description" : "Fill color used in logo", diff --git a/ios-sdk b/ios-sdk index 7ca86d879..b66dc8431 160000 --- a/ios-sdk +++ b/ios-sdk @@ -1 +1 @@ -Subproject commit 7ca86d879b42f9336ff1ba00d8ead4006eb63388 +Subproject commit b66dc8431ea634337fa7f97f5021f312282f09c0 diff --git a/ownCloud Action Extension/Info.plist b/ownCloud Action Extension/Info.plist new file mode 100644 index 000000000..244eda26e --- /dev/null +++ b/ownCloud Action Extension/Info.plist @@ -0,0 +1,50 @@ + + + + + OCKeychainAccessGroupIdentifier + group.com.owncloud.ios-app + CFBundleIcons + + CFBundlePrimaryIcon + + CFBundleIconFiles + + branding-action-extension-icon + + + + OCHasFileProvider + + OCAppComponentIdentifier + shareExtension + CFBundleDisplayName + Save to $(APP_PRODUCT_NAME) + OCAppIdentifierPrefix + $(AppIdentifierPrefix) + OCAppGroupIdentifier + group.com.owncloud.ios-app + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + SUBQUERY ( + extensionItems, + $extensionItem, + SUBQUERY ( + $extensionItem.attachments, + $attachment, + ( + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data" + ) + ).@count == $extensionItem.attachments.@count +).@count > 0 + + NSExtensionPrincipalClass + ShareExtensionViewController + NSExtensionPointIdentifier + com.apple.ui-services + + + diff --git a/ownCloud Action Extension/de.lproj/InfoPlist.strings b/ownCloud Action Extension/de.lproj/InfoPlist.strings new file mode 100644 index 000000000..681bcc143 --- /dev/null +++ b/ownCloud Action Extension/de.lproj/InfoPlist.strings @@ -0,0 +1,22 @@ +/* + InfoPlist.strings + ownCloud + + Created by Matthias Hühne on 11/20/2023. + Copyright © 2020 ownCloud GmbH. All rights reserved. +*/ + +/* + * Copyright (C) 2020, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +"CFBundleDisplayName" = "Speichern in ownCloud"; + +"The Share Extension is not available on this system." = "Die Erweiterung ist auf diesem System nicht verfügbar."; +"Share Extension unavailable" = "Erweiterung nicht verfügbar"; diff --git a/ownCloud Action Extension/en.lproj/InfoPlist.strings b/ownCloud Action Extension/en.lproj/InfoPlist.strings new file mode 100644 index 000000000..edfd3700d --- /dev/null +++ b/ownCloud Action Extension/en.lproj/InfoPlist.strings @@ -0,0 +1,22 @@ +/* + InfoPlist.strings + ownCloud + + Created by Matthias Hühne on 11/20/2023. + Copyright © 2020 ownCloud GmbH. All rights reserved. +*/ + +/* + * Copyright (C) 2020, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +"CFBundleDisplayName" = "Save to ownCloud"; + +"The Share Extension is not available on this system." = "The Share Extension is not available on this system."; +"Share Extension unavailable" = "Share Extension unavailable"; diff --git a/ownCloud Action Extension/ownCloud Action Extension.entitlements b/ownCloud Action Extension/ownCloud Action Extension.entitlements new file mode 100644 index 000000000..988c47786 --- /dev/null +++ b/ownCloud Action Extension/ownCloud Action Extension.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + group.com.owncloud.ios-app + + keychain-access-groups + + $(AppIdentifierPrefix)group.com.owncloud.ios-app + + + diff --git a/ownCloud File Provider UI/CancelLabelViewController.swift b/ownCloud File Provider UI/CancelLabelViewController.swift index 60d5fd312..240368673 100644 --- a/ownCloud File Provider UI/CancelLabelViewController.swift +++ b/ownCloud File Provider UI/CancelLabelViewController.swift @@ -26,9 +26,11 @@ class CancelLabelViewController: UIViewController { @IBOutlet var label : UILabel! @IBOutlet var button : ThemeButton! - var cancelAction: (() -> Void)? + typealias CancelAction = (() -> Void) - func updateCancelLabels(with message: String) { + var cancelAction: CancelAction? + + func updateCancelLabels(with message: String, buttonLabel: String? = nil) { let collection = Theme.shared.activeCollection view.cssSelector = .toolbar @@ -38,7 +40,7 @@ class CancelLabelViewController: UIViewController { label.apply(css: collection.css, properties: [.stroke]) self.label.text = message - self.button.setTitle("Cancel".localized, for: .normal) + self.button.setTitle(buttonLabel ?? "Cancel".localized, for: .normal) } @IBAction func cancelScreen() { diff --git a/ownCloud File Provider UI/DocumentActionViewController.swift b/ownCloud File Provider UI/DocumentActionViewController.swift index 96cd62690..76a80b081 100644 --- a/ownCloud File Provider UI/DocumentActionViewController.swift +++ b/ownCloud File Provider UI/DocumentActionViewController.swift @@ -106,7 +106,7 @@ class DocumentActionViewController: FPUIActionExtensionViewController { prepareNavigationController() - showCancelLabel(with: "Connecting…".localized) + showMessage(with: "Connecting…".localized) var actionTypeLabel = "" var actionExtensionType : ActionExtensionType = .undefined @@ -139,7 +139,7 @@ class DocumentActionViewController: FPUIActionExtensionViewController { OnMainThread { if actionExtensionType == .sharing, core.connection.capabilities?.sharingAPIEnabled == false || item.isShareable == false { - self.showCancelLabel(with: String(format: "%@ is not available for this item.".localized, actionTypeLabel)) + self.showMessage(with: String(format: "%@ is not available for this item.".localized, actionTypeLabel)) } else if core.connectionStatus == .online { self.coreConnectionStatusObservation?.invalidate() self.coreConnectionStatusObservation = nil @@ -157,18 +157,18 @@ class DocumentActionViewController: FPUIActionExtensionViewController { } } else if core.connectionStatus == .connecting { triedConnecting = true - self.showCancelLabel(with: "Connecting…".localized) + self.showMessage(with: "Connecting…".localized) } else if core.connectionStatus == .offline || core.connectionStatus == .unavailable { // Display error if `.connecting` isn't reached within 2 seconds OnMainThread(after: 2) { if !triedConnecting { - self.showCancelLabel(with: String(format: "%@ is not available, when this account is offline. Please open the app and log into your account before you can do this action.".localized, actionTypeLabel)) + self.showMessage(with: String(format: "%@ is not available, when this account is offline. Please open the app and log into your account before you can do this action.".localized, actionTypeLabel)) } } // Display error if `.connecting` has already been reached if triedConnecting { - self.showCancelLabel(with: String(format: "%@ is not available, when this account is offline. Please open the app and log into your account before you can do this action.".localized, actionTypeLabel)) + self.showMessage(with: String(format: "%@ is not available, when this account is offline. Please open the app and log into your account before you can do this action.".localized, actionTypeLabel)) } } } @@ -180,7 +180,18 @@ class DocumentActionViewController: FPUIActionExtensionViewController { override func prepare(forError error: Error) { if !OCFileProviderSettings.browseable { prepareNavigationController() - showCancelLabel(with: "File Provider access has been disabled by the administrator.\n\nPlease use the app to access your files.".localized) + showMessage(with: "File Provider access has been disabled by the administrator.\n\nPlease use the app to access your files.".localized) + return + } + + if OCBookmarkManager.shared.bookmarks.count == 0 { + prepareNavigationController() + showMessage(with: "No account has been set up in the {{app.name}} app yet.".localized, buttonLabel: "Open app".localized, action: { [weak self] in + if let appURLScheme = OCAppIdentity.shared.appURLSchemes?.first { + self?.extensionContext.open(URL(string: "\(appURLScheme)://fp-no-account")!) + } + self?.complete() + }) return } @@ -197,20 +208,29 @@ class DocumentActionViewController: FPUIActionExtensionViewController { AppLockManager.shared.showLockscreenIfNeeded() } else { prepareNavigationController() - showCancelLabel(with: "Passcode protection is not supported on this device.\nPlease disable passcode lock in the app settings.".localized) + showMessage(with: "Passcode protection is not supported on this device.\nPlease disable passcode lock in the app settings.".localized) } } - func showCancelLabel(with message: String) { + func showMessage(with message: String, buttonLabel: String? = nil, action: CancelLabelViewController.CancelAction? = nil) { OnMainThread { + var messageController: CancelLabelViewController? + if let currentController = self.themeNavigationController?.viewControllers.first as? CancelLabelViewController { - currentController.updateCancelLabels(with: message) + messageController = currentController } else if let cancelLabelViewController = UIStoryboard.init(name: "MainInterface", bundle: nil).instantiateViewController(withIdentifier: "CancelLabelViewController") as? CancelLabelViewController { - cancelLabelViewController.updateCancelLabels(with: message) - cancelLabelViewController.cancelAction = { [weak self] in + messageController = cancelLabelViewController + } + + if let messageController { + messageController.updateCancelLabels(with: message, buttonLabel: buttonLabel) + messageController.cancelAction = action ?? { [weak self] in self?.complete(cancelWith: NSError(domain: FPUIErrorDomain, code: Int(FPUIExtensionErrorCode.userCancelled.rawValue), userInfo: nil)) } - self.themeNavigationController?.viewControllers = [ cancelLabelViewController ] + + if self.themeNavigationController?.viewControllers.first != messageController { + self.themeNavigationController?.viewControllers = [ messageController ] + } } } } diff --git a/ownCloud File Provider/FileProviderExtension.m b/ownCloud File Provider/FileProviderExtension.m index 985a9ed62..527588bfa 100644 --- a/ownCloud File Provider/FileProviderExtension.m +++ b/ownCloud File Provider/FileProviderExtension.m @@ -1071,6 +1071,8 @@ - (NSProgress *)fetchThumbnailsForItemIdentifiers:(NSArray 256) @@ -1169,7 +1171,7 @@ - (OCCore *)core - (OCCore *)coreWithError:(NSError **)outError { - OCLogDebug(@"FileProviderExtension[%p].core[enter]: _core=%p, bookmark=%@", self, _core, self.bookmark); + OCLogVerbose(@"FileProviderExtension[%p].core[enter]: _core=%p, bookmark=%@", self, _core, self.bookmark); OCBookmark *bookmark = self.bookmark; __block OCCore *retCore = nil; @@ -1253,7 +1255,7 @@ - (OCCore *)coreWithError:(NSError **)outError *outError = retError; } - OCLogDebug(@"FileProviderExtension[%p].core[leave]: _core=%p, bookmark=%@", self, retCore, bookmark); + OCLogVerbose(@"FileProviderExtension[%p].core[leave]: _core=%p, bookmark=%@", self, retCore, bookmark); return (retCore); diff --git a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m index 1ce929d8a..4c9bfd82f 100644 --- a/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m +++ b/ownCloud File Provider/FileProviderExtensionThumbnailRequest.m @@ -22,7 +22,7 @@ @interface FileProviderExtensionThumbnailRequest () { BOOL _isDone; - OCResourceRequestItemThumbnail *_thumbnailRequest; + __weak OCResourceRequestItemThumbnail *_thumbnailRequest; } @end @@ -102,7 +102,7 @@ - (void)requestNextThumbnail dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ NSError *returnError = (thumbnail==nil) ? - ((error != nil) ? error.translatedError : OCError(OCErrorInternal)) : + ((error != nil) ? error.translatedError : nil) : nil; self.perThumbnailCompletionHandler(itemIdentifier, thumbnail.data, returnError); diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 49c183fed..8bacbe3de 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -36,13 +36,17 @@ 39057AA3233BA7A60008E6C0 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 39057AAA233BA7A60008E6C0 /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; 39057AA4233BA7A60008E6C0 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 39057AAA233BA7A60008E6C0 /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; 39057AA7233BA7A60008E6C0 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 39057AAA233BA7A60008E6C0 /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; - 391130112A20E3F200C22DD2 /* branding-sidebar-link-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 391130102A20E3F100C22DD2 /* branding-sidebar-link-icon.png */; }; 3912208223436EB80026C290 /* SortMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3912208123436EB80026C290 /* SortMethod.swift */; }; 3912D8AD29958BF400EDCB9A /* OCThemeValues.h in Headers */ = {isa = PBXBuildFile; fileRef = 3912D8AB29958BF400EDCB9A /* OCThemeValues.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3912D8AE29958BF400EDCB9A /* OCThemeValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 3912D8AC29958BF400EDCB9A /* OCThemeValues.m */; }; + 391933C22B0B81DB007DF90B /* ownCloud Action Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 391933B42B0B81DB007DF90B /* ownCloud Action Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 391933C62B0B8E5A007DF90B /* ShareExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9B4FC42940F8D60037F8F8 /* ShareExtensionViewController.swift */; }; + 391933CD2B0B8EBD007DF90B /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; + 391933CE2B0B8EC0007DF90B /* ownCloudAppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */; }; + 391933CF2B0B8EC3007DF90B /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; + 391933D02B0B8EC7007DF90B /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; + 391933DA2B0B9863007DF90B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 391933DC2B0B9863007DF90B /* InfoPlist.strings */; }; 391C79A824E186DC00CB6333 /* OCBookmark+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC136581208223F000FC0F60 /* OCBookmark+Extension.swift */; }; - 392378FF24EBD1A1006E86DE /* branding-splashscreen.png in Resources */ = {isa = PBXBuildFile; fileRef = 39F48A6624D848550000E3F9 /* branding-splashscreen.png */; }; - 3923790524EBD1A5006E86DE /* branding-splashscreen-background.png in Resources */ = {isa = PBXBuildFile; fileRef = 39F48A6724D848550000E3F9 /* branding-splashscreen-background.png */; }; 392CFEB72705831700631D2B /* LAContext+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392CFEB62705831700631D2B /* LAContext+Extension.swift */; }; 392DDAE624C8923B009E5406 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 233BDEA6204FEFE500C06732 /* Assets.xcassets */; }; 392DDB1424CF024D009E5406 /* ImportFilesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392DDB1324CF024C009E5406 /* ImportFilesController.swift */; }; @@ -54,7 +58,6 @@ 394A0B0122EEFC2C00603813 /* ownCloudAppShared.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 394A0B0922EEFCE400603813 /* ownCloudAppShared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */; }; 394A0B0A22EEFCF500603813 /* ownCloudSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 239369782076110900BCE21A /* ownCloudSDK.framework */; }; - 394B0CFC29F958200005CBFE /* AccountSettingsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394B0CFB29F958200005CBFE /* AccountSettingsProvider.swift */; }; 394E1FDA233E2D64009D2897 /* FavoriteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394E1FD9233E2D64009D2897 /* FavoriteAction.swift */; }; 394E1FDC233E3750009D2897 /* UnfavoriteAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394E1FDB233E3750009D2897 /* UnfavoriteAction.swift */; }; 39534BC724EA903200AD7907 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 39534BC924EA903200AD7907 /* InfoPlist.strings */; }; @@ -65,8 +68,6 @@ 396C82FB2319AFDD00938262 /* CollaborateAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396C82FA2319AFDD00938262 /* CollaborateAction.swift */; }; 396D7C6523224A53002380C1 /* DiscardSceneAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7C5F23224A53002380C1 /* DiscardSceneAction.swift */; }; 397754F82327A33500119FCB /* OpenSceneAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397754F22327A33500119FCB /* OpenSceneAction.swift */; }; - 398393BE246D63B0001A212B /* branding-login-background.png in Resources */ = {isa = PBXBuildFile; fileRef = 398393BC246D63B0001A212B /* branding-login-background.png */; }; - 398393BF246D63B0001A212B /* branding-login-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 398393BD246D63B0001A212B /* branding-login-logo.png */; }; 39878B7421FB1DE800DBF693 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39878B7321FB1DE800DBF693 /* UINavigationController+Extension.swift */; }; 399697F5260255B100E5AEBA /* PDFGotoPageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399697F1260255B100E5AEBA /* PDFGotoPageAction.swift */; }; 399698ED260A3CEE00E5AEBA /* ImportPasteboardAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391220922344C30F0026C290 /* ImportPasteboardAction.swift */; }; @@ -98,7 +99,6 @@ 39E104CA24C585C30085FDDD /* (null) in Resources */ = {isa = PBXBuildFile; }; 39E6DE86233CDF1E008DAE04 /* OCItemTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E6DE85233CDF1E008DAE04 /* OCItemTracker.swift */; }; 39EF06B325D6C3FC001E1E19 /* PresentationModeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39EF06AF25D6C3FC001E1E19 /* PresentationModeAction.swift */; }; - 39F48A6A24D89D7E0000E3F9 /* branding-bookmark-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 39F48A6524D847D70000E3F9 /* branding-bookmark-icon.png */; }; 4C05D8A5238708D40073EF50 /* MediaUploadStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C05D8A4238708D40073EF50 /* MediaUploadStorage.swift */; }; 4C11EE5B22E88D4200B84869 /* InstantMediaUploadTaskExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C11EE5A22E88D4200B84869 /* InstantMediaUploadTaskExtension.swift */; }; 4C1561E8222321E0009C4EF3 /* PhotoSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1561E7222321E0009C4EF3 /* PhotoSelectionViewController.swift */; }; @@ -223,6 +223,11 @@ DC20DE6B21C01B210096000B /* ownCloudUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2393697C2076110900BCE21A /* ownCloudUI.framework */; }; DC2218C62822C5B900808BCE /* OCVFSNode+FileProviderItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2218C52822C5B900808BCE /* OCVFSNode+FileProviderItem.m */; }; DC2218CC2823329100808BCE /* FileProviderContentEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2218CB2823329100808BCE /* FileProviderContentEnumerator.m */; }; + DC2323DC2AA7B5D600BFF393 /* BookmarkComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2323DB2AA7B5D600BFF393 /* BookmarkComposer.swift */; }; + DC2323DE2AA7B6BB00BFF393 /* BookmarkComposerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2323DD2AA7B6BB00BFF393 /* BookmarkComposerConfiguration.swift */; }; + DC2323E02AA7C59400BFF393 /* BookmarkSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2323DF2AA7C59400BFF393 /* BookmarkSetupViewController.swift */; }; + DC2323E32AA85D0300BFF393 /* BookmarkSetupStepViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2323E22AA85D0300BFF393 /* BookmarkSetupStepViewController.swift */; }; + DC2323E62AA865A700BFF393 /* BookmarkSetupStepEnterURLViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2323E52AA865A700BFF393 /* BookmarkSetupStepEnterURLViewController.swift */; }; DC23D1D9238F390A00423F62 /* OCLicenseAppStoreReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = DC23D1D7238F390200423F62 /* OCLicenseAppStoreReceipt.m */; }; DC23D1DA238F391200423F62 /* OCLicenseAppStoreReceipt.h in Headers */ = {isa = PBXBuildFile; fileRef = DC23D1D6238F390200423F62 /* OCLicenseAppStoreReceipt.h */; settings = {ATTRIBUTES = (Public, ); }; }; DC24B28725BA2A2E005783E2 /* Branding.h in Headers */ = {isa = PBXBuildFile; fileRef = DC24B27125B9DF31005783E2 /* Branding.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -244,6 +249,7 @@ DC27A1A520CBEF85008ACB6C /* OCBookmark+FileProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1A420CBEF85008ACB6C /* OCBookmark+FileProvider.m */; }; DC27A1A820CC095C008ACB6C /* OCCore+FileProviderTools.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1A720CC095C008ACB6C /* OCCore+FileProviderTools.m */; }; DC27A1E920CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = DC27A1E820CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m */; }; + DC28297E2AAF02A800BFF393 /* BookmarkSetupStepIntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28297D2AAF02A800BFF393 /* BookmarkSetupStepIntroViewController.swift */; }; DC28F826294B733700AC4013 /* OCItemPolicy+Interactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28F825294B733700AC4013 /* OCItemPolicy+Interactions.swift */; }; DC28F828294BB5ED00AC4013 /* SortedItemDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28F827294BB5ED00AC4013 /* SortedItemDataSource.swift */; }; DC298C922934CF56009FA87F /* AccountConnectionErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC298C8C2934B3E7009FA87F /* AccountConnectionErrorHandler.swift */; }; @@ -303,6 +309,11 @@ DC4C575D233958B70098BAE9 /* FixedHeightImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4C575C233958B70098BAE9 /* FixedHeightImageView.swift */; }; DC51FD922475715F0069AB79 /* CellularSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC51FD912475715F0069AB79 /* CellularSettingsViewController.swift */; }; DC576EC022647A070087316D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DC576EC222647A070087316D /* Localizable.strings */; }; + DC5908752AA87A1700BFF393 /* BookmarkSetupStepEnterUsernameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5908742AA87A1700BFF393 /* BookmarkSetupStepEnterUsernameViewController.swift */; }; + DC5908772AA87ABF00BFF393 /* BookmarkSetupStepAuthenticateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5908762AA87ABF00BFF393 /* BookmarkSetupStepAuthenticateViewController.swift */; }; + DC59087A2AA87F6B00BFF393 /* BookmarkSetupStepFinishedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5908792AA87F6B00BFF393 /* BookmarkSetupStepFinishedViewController.swift */; }; + DC59087D2AA8B82200BFF393 /* BookmarkSetupStepPrepopulateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59087C2AA8B82200BFF393 /* BookmarkSetupStepPrepopulateViewController.swift */; }; + DC59087F2AA8D25400BFF393 /* CertificateSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC59087E2AA8D25400BFF393 /* CertificateSummaryView.swift */; }; DC5C48A32918FB7400EBC053 /* CollectionSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5C48A22918FB7400EBC053 /* CollectionSidebarViewController.swift */; }; DC5D58FF2A7166A300BFF393 /* ThemeCSS+SystemColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC5D58FE2A7166A300BFF393 /* ThemeCSS+SystemColors.swift */; }; DC60F2A629802ABE00905EC8 /* UINavigationItem+NavigationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC60F2A529802ABE00905EC8 /* UINavigationItem+NavigationContent.swift */; }; @@ -397,6 +408,8 @@ DC9BFBB320A19AF4007064B5 /* doc in Resources */ = {isa = PBXBuildFile; fileRef = DC9BFBB220A19AF3007064B5 /* doc */; }; DC9BFBBD20A1C37B007064B5 /* PasswordManagerAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9BFBBC20A1C37B007064B5 /* PasswordManagerAccess.swift */; }; DC9C1AEC247C76470067895A /* MessageGroupCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9C1AEB247C76470067895A /* MessageGroupCell.swift */; }; + DCA05A602AE6603E00BFF393 /* ClientLocationPopupButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA05A5F2AE6603E00BFF393 /* ClientLocationPopupButton.swift */; }; + DCA05A622AE664F200BFF393 /* OCAction+UIAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA05A612AE664F200BFF393 /* OCAction+UIAction.swift */; }; DCA2EDE2279B16F1001F04E6 /* ResourceSourceItemIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA2EDE1279B16F1001F04E6 /* ResourceSourceItemIcons.swift */; }; DCA2EDE4279B1789001F04E6 /* ResourceItemIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA2EDE3279B1789001F04E6 /* ResourceItemIcon.swift */; }; DCA35D3F24CEDA5200DBE2B0 /* DiagnosticViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA35D3E24CEDA5200DBE2B0 /* DiagnosticViewController.swift */; }; @@ -499,7 +512,6 @@ DCD864122811FC5700CA6631 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD864112811FC5700CA6631 /* GradientView.swift */; }; DCD954DF247D62FA00E184E6 /* MessageTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD954DE247D62FA00E184E6 /* MessageTableViewController.swift */; }; DCD9B87B2379612B00691929 /* OCLicenseManager+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = DCD9B873237960E600691929 /* OCLicenseManager+Internal.h */; }; - DCDA83852A9CE6C300BFF393 /* InitialSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDA83842A9CE6C300BFF393 /* InitialSetupViewController.swift */; }; DCDBB60A2525305600FAD707 /* NotificationAuthErrorForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = DCDBB60225252FDA00FAD707 /* NotificationAuthErrorForwarder.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCDBB60B2525306000FAD707 /* NotificationAuthErrorForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = DCDBB60325252FDA00FAD707 /* NotificationAuthErrorForwarder.m */; }; DCDC0ACF23CD186400DFE36D /* ownCloudApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC0855C2293F1FD008CC05C /* ownCloudApp.framework */; }; @@ -563,6 +575,7 @@ DCE684F6241BD4E800799C30 /* Branding.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3931206A2326451900E8DFBA /* Branding.plist */; }; DCEA7F41282D3B110050A3C0 /* VFSManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEA7F3F282D3B110050A3C0 /* VFSManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; DCEA7F42282D3B110050A3C0 /* VFSManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEA7F40282D3B110050A3C0 /* VFSManager.m */; }; + DCEA89822AD84D6000BFF393 /* BrandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEA89812AD84D6000BFF393 /* BrandView.swift */; }; DCEAF06D280767CF00980B6D /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF06C280767CF00980B6D /* OpenSSL */; }; DCEAF08A2808254800980B6D /* DriveListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCEAF0892808254800980B6D /* DriveListCell.swift */; }; DCEAF08D28084B3800980B6D /* Down in Frameworks */ = {isa = PBXBuildFile; productRef = DCEAF08C28084B3800980B6D /* Down */; }; @@ -652,6 +665,34 @@ remoteGlobalIDString = DC7E0A77203732B3006111FA; remoteInfo = Ocean; }; + 391933C02B0B81DB007DF90B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 233BDE94204FEFE500C06732 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 391933B32B0B81DB007DF90B; + remoteInfo = "ownCloud Action Extension"; + }; + 391933C72B0B8EA2007DF90B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 233BDE94204FEFE500C06732 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DCC0855B2293F1FD008CC05C; + remoteInfo = ownCloudApp; + }; + 391933C92B0B8EA5007DF90B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 233BDE94204FEFE500C06732 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 394A0AF822EEFC2C00603813; + remoteInfo = ownCloudAppShared; + }; + 391933CB2B0B8EA9007DF90B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = DCC8F9AA202852A200EB6701; + remoteInfo = ownCloudSDK; + }; 394A0AFE22EEFC2C00603813 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 233BDE94204FEFE500C06732 /* Project object */; @@ -757,6 +798,13 @@ remoteGlobalIDString = DCC8F9AA202852A200EB6701; remoteInfo = ownCloudSDK; }; + DC2A6C752AC6BE0100BFF393 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = DC3094832057358800189B9A; + remoteInfo = ownCloudUI; + }; DC3BE0CE2077BC52002A0AC0 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 233BDEBF204FEFF300C06732 /* ownCloudSDK.xcodeproj */; @@ -922,6 +970,7 @@ 39A7138722E79C6700089423 /* ownCloud Intents.appex in Embed Foundation Extensions */, DCE4E47224C1F5610051722F /* ownCloud Share Extension.appex in Embed Foundation Extensions */, DCC6566520C9B7E400110A97 /* ownCloud File Provider.appex in Embed Foundation Extensions */, + 391933C22B0B81DB007DF90B /* ownCloud Action Extension.appex in Embed Foundation Extensions */, 39DC7CD725C2E1570001E08C /* ownCloud File Provider UI.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; @@ -972,12 +1021,17 @@ 39057AA9233BA7A60008E6C0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; 39057AB1233BA7AE0008E6C0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; 39104E0A223991C8002FC02F /* UIButton+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = ""; }; - 391130102A20E3F100C22DD2 /* branding-sidebar-link-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "branding-sidebar-link-icon.png"; sourceTree = ""; }; 3912208123436EB80026C290 /* SortMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortMethod.swift; sourceTree = ""; }; 391220922344C30F0026C290 /* ImportPasteboardAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportPasteboardAction.swift; sourceTree = ""; }; 391220932344C30F0026C290 /* CutAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CutAction.swift; sourceTree = ""; }; 3912D8AB29958BF400EDCB9A /* OCThemeValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCThemeValues.h; sourceTree = ""; }; 3912D8AC29958BF400EDCB9A /* OCThemeValues.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCThemeValues.m; sourceTree = ""; }; + 391933B42B0B81DB007DF90B /* ownCloud Action Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "ownCloud Action Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 391933B52B0B81DB007DF90B /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; + 391933BF2B0B81DB007DF90B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 391933D12B0B9577007DF90B /* ownCloud Action Extension.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "ownCloud Action Extension.entitlements"; sourceTree = ""; }; + 391933DB2B0B9863007DF90B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 391933DD2B0B9871007DF90B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 392CFEB62705831700631D2B /* LAContext+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LAContext+Extension.swift"; sourceTree = ""; }; 392DDB1324CF024C009E5406 /* ImportFilesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportFilesController.swift; sourceTree = ""; }; 3931206A2326451900E8DFBA /* Branding.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Branding.plist; path = ownCloud/Resources/Theming/Branding.plist; sourceTree = SOURCE_ROOT; }; @@ -985,7 +1039,6 @@ 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ownCloudAppShared.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 394A0AFB22EEFC2C00603813 /* ownCloudAppShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ownCloudAppShared.h; sourceTree = ""; }; 394A0AFC22EEFC2C00603813 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 394B0CFB29F958200005CBFE /* AccountSettingsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingsProvider.swift; sourceTree = ""; }; 394E1FD9233E2D64009D2897 /* FavoriteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteAction.swift; sourceTree = ""; }; 394E1FDB233E3750009D2897 /* UnfavoriteAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnfavoriteAction.swift; sourceTree = ""; }; 39534BC824EA903200AD7907 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1019,8 +1072,6 @@ 397754E123279EED00119FCB /* OCItem+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OCItem+Extension.swift"; sourceTree = ""; }; 397754F22327A33500119FCB /* OpenSceneAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSceneAction.swift; sourceTree = ""; }; 397E276B23D05A5400117B07 /* ServerListToolCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerListToolCell.swift; sourceTree = ""; }; - 398393BC246D63B0001A212B /* branding-login-background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "branding-login-background.png"; path = "ownCloud/Resources/Theming/branding-login-background.png"; sourceTree = SOURCE_ROOT; }; - 398393BD246D63B0001A212B /* branding-login-logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "branding-login-logo.png"; path = "ownCloud/Resources/Theming/branding-login-logo.png"; sourceTree = SOURCE_ROOT; }; 3984F56B2319202200DC2639 /* DeletePathItemIntentHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeletePathItemIntentHandler.swift; sourceTree = ""; }; 39878B7321FB1DE800DBF693 /* UINavigationController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extension.swift"; sourceTree = ""; }; 39880BAA233B5236006EA539 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Localizable.strings; sourceTree = ""; }; @@ -1101,9 +1152,6 @@ 39E42D1B2315288B00B82AC3 /* KeyCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCommands.swift; sourceTree = ""; }; 39E6DE85233CDF1E008DAE04 /* OCItemTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCItemTracker.swift; sourceTree = ""; }; 39EF06AF25D6C3FC001E1E19 /* PresentationModeAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationModeAction.swift; sourceTree = ""; }; - 39F48A6524D847D70000E3F9 /* branding-bookmark-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "branding-bookmark-icon.png"; path = "ownCloud/Resources/Theming/branding-bookmark-icon.png"; sourceTree = SOURCE_ROOT; }; - 39F48A6624D848550000E3F9 /* branding-splashscreen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "branding-splashscreen.png"; path = "ownCloud/Resources/Theming/branding-splashscreen.png"; sourceTree = SOURCE_ROOT; }; - 39F48A6724D848550000E3F9 /* branding-splashscreen-background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "branding-splashscreen-background.png"; path = "ownCloud/Resources/Theming/branding-splashscreen-background.png"; sourceTree = SOURCE_ROOT; }; 39F689A922EF5EDC00E63429 /* ownCloud Intents.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ownCloud Intents.entitlements"; sourceTree = ""; }; 39F689AA22F018C100E63429 /* GetDirectoryListingIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetDirectoryListingIntentHandler.swift; sourceTree = ""; }; 42866B2892DC9EDC65D844E7 /* Pods_ownCloud_Screenshots_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ownCloud_Screenshots_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1206,7 +1254,6 @@ DC081C8A299B9B9000BFF393 /* AppStateActionGoToPersonalFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateActionGoToPersonalFolder.swift; sourceTree = ""; }; DC0A35A024C1091400FB58FC /* UserInterfaceContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInterfaceContext.swift; sourceTree = ""; }; DC0A5C422550C70800E6674B /* class-settings-sdk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "class-settings-sdk"; path = "ios-sdk/doc/class-settings-sdk"; sourceTree = SOURCE_ROOT; }; - DC0B379320514E4700189B9A /* ServerListBookmarkCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListBookmarkCell.swift; sourceTree = ""; }; DC0B37952051541C00189B9A /* ownCloud.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ownCloud.entitlements; sourceTree = ""; }; DC0B37962051681600189B9A /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = ""; }; DC0CE19128C7DBE3009ABDFB /* OpenInWebAppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInWebAppAction.swift; sourceTree = ""; }; @@ -1218,6 +1265,11 @@ DC2218C52822C5B900808BCE /* OCVFSNode+FileProviderItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCVFSNode+FileProviderItem.m"; sourceTree = ""; }; DC2218CA2823329100808BCE /* FileProviderContentEnumerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderContentEnumerator.h; sourceTree = ""; }; DC2218CB2823329100808BCE /* FileProviderContentEnumerator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderContentEnumerator.m; sourceTree = ""; }; + DC2323DB2AA7B5D600BFF393 /* BookmarkComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkComposer.swift; sourceTree = ""; }; + DC2323DD2AA7B6BB00BFF393 /* BookmarkComposerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkComposerConfiguration.swift; sourceTree = ""; }; + DC2323DF2AA7C59400BFF393 /* BookmarkSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkSetupViewController.swift; sourceTree = ""; }; + DC2323E22AA85D0300BFF393 /* BookmarkSetupStepViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkSetupStepViewController.swift; sourceTree = ""; }; + DC2323E52AA865A700BFF393 /* BookmarkSetupStepEnterURLViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkSetupStepEnterURLViewController.swift; sourceTree = ""; }; DC23D1D6238F390200423F62 /* OCLicenseAppStoreReceipt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OCLicenseAppStoreReceipt.h; sourceTree = ""; }; DC23D1D7238F390200423F62 /* OCLicenseAppStoreReceipt.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCLicenseAppStoreReceipt.m; sourceTree = ""; }; DC243BF92317B446004FBB5C /* ThemeWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeWindow.swift; sourceTree = ""; }; @@ -1246,6 +1298,7 @@ DC27A1A720CC095C008ACB6C /* OCCore+FileProviderTools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "OCCore+FileProviderTools.m"; sourceTree = ""; }; DC27A1E720CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileProviderExtensionThumbnailRequest.h; sourceTree = ""; }; DC27A1E820CC56B0008ACB6C /* FileProviderExtensionThumbnailRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileProviderExtensionThumbnailRequest.m; sourceTree = ""; }; + DC28297D2AAF02A800BFF393 /* BookmarkSetupStepIntroViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkSetupStepIntroViewController.swift; sourceTree = ""; }; DC28F825294B733700AC4013 /* OCItemPolicy+Interactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCItemPolicy+Interactions.swift"; sourceTree = ""; }; DC28F827294BB5ED00AC4013 /* SortedItemDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortedItemDataSource.swift; sourceTree = ""; }; DC298C8C2934B3E7009FA87F /* AccountConnectionErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountConnectionErrorHandler.swift; sourceTree = ""; }; @@ -1307,6 +1360,11 @@ DC4FEAE9209E48E800D4476B /* DispatchQueueTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueTools.swift; sourceTree = ""; }; DC51FD912475715F0069AB79 /* CellularSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellularSettingsViewController.swift; sourceTree = ""; }; DC576EC122647A070087316D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + DC5908742AA87A1700BFF393 /* BookmarkSetupStepEnterUsernameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkSetupStepEnterUsernameViewController.swift; sourceTree = ""; }; + DC5908762AA87ABF00BFF393 /* BookmarkSetupStepAuthenticateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkSetupStepAuthenticateViewController.swift; sourceTree = ""; }; + DC5908792AA87F6B00BFF393 /* BookmarkSetupStepFinishedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkSetupStepFinishedViewController.swift; sourceTree = ""; }; + DC59087C2AA8B82200BFF393 /* BookmarkSetupStepPrepopulateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkSetupStepPrepopulateViewController.swift; sourceTree = ""; }; + DC59087E2AA8D25400BFF393 /* CertificateSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateSummaryView.swift; sourceTree = ""; }; DC5C48A22918FB7400EBC053 /* CollectionSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionSidebarViewController.swift; sourceTree = ""; }; DC5D58FE2A7166A300BFF393 /* ThemeCSS+SystemColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeCSS+SystemColors.swift"; sourceTree = ""; }; DC5D9E742496512400BFFE8E /* MessageQueueExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageQueueExample.swift; sourceTree = ""; }; @@ -1351,6 +1409,7 @@ DC6C68352574FD0400E46BD4 /* PLCrashReporter.LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = PLCrashReporter.LICENSE; sourceTree = ""; }; DC6CC3142642C3560040ECAC /* ExternalBrowserBusyHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalBrowserBusyHandler.swift; sourceTree = ""; }; DC6CF7FA219446050013B9F9 /* LogSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogSettingsViewController.swift; sourceTree = ""; }; + DC6F0B962AE90C4A00BFF393 /* com.owncloud.ios-app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "com.owncloud.ios-app"; sourceTree = ""; }; DC6FDAF62953AD50004F0C7F /* ClientSharedWithMeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientSharedWithMeViewController.swift; sourceTree = ""; }; DC70398326128B89009F2DC1 /* NSString+ByteCountParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+ByteCountParser.h"; sourceTree = ""; }; DC70398426128B89009F2DC1 /* NSString+ByteCountParser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+ByteCountParser.m"; sourceTree = ""; }; @@ -1409,6 +1468,8 @@ DC9BFBBA20A1B3CA007064B5 /* icon-password-manager.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "icon-password-manager.tvg"; path = "img/filetypes-tvg/icon-password-manager.tvg"; sourceTree = SOURCE_ROOT; }; DC9BFBBC20A1C37B007064B5 /* PasswordManagerAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordManagerAccess.swift; sourceTree = ""; }; DC9C1AEB247C76470067895A /* MessageGroupCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageGroupCell.swift; sourceTree = ""; }; + DCA05A5F2AE6603E00BFF393 /* ClientLocationPopupButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientLocationPopupButton.swift; sourceTree = ""; }; + DCA05A612AE664F200BFF393 /* OCAction+UIAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCAction+UIAction.swift"; sourceTree = ""; }; DCA2EDE1279B16F1001F04E6 /* ResourceSourceItemIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceSourceItemIcons.swift; sourceTree = ""; }; DCA2EDE3279B1789001F04E6 /* ResourceItemIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceItemIcon.swift; sourceTree = ""; }; DCA35D3E24CEDA5200DBE2B0 /* DiagnosticViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticViewController.swift; sourceTree = ""; }; @@ -1524,7 +1585,6 @@ DCD864112811FC5700CA6631 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; DCD954DE247D62FA00E184E6 /* MessageTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTableViewController.swift; sourceTree = ""; }; DCD9B873237960E600691929 /* OCLicenseManager+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCLicenseManager+Internal.h"; sourceTree = ""; }; - DCDA83842A9CE6C300BFF393 /* InitialSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialSetupViewController.swift; sourceTree = ""; }; DCDBB60225252FDA00FAD707 /* NotificationAuthErrorForwarder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationAuthErrorForwarder.h; sourceTree = ""; }; DCDBB60325252FDA00FAD707 /* NotificationAuthErrorForwarder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationAuthErrorForwarder.m; sourceTree = ""; }; DCDC0AD023CD18D200DFE36D /* OCLicenseManager+Setup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCLicenseManager+Setup.swift"; sourceTree = ""; }; @@ -1569,11 +1629,13 @@ DCE5E89E2080D780005F60CE /* text-vcard.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "text-vcard.tvg"; path = "img/filetypes-tvg/text-vcard.tvg"; sourceTree = SOURCE_ROOT; }; DCE5E89F2080D780005F60CE /* folder-shared.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "folder-shared.tvg"; path = "img/filetypes-tvg/folder-shared.tvg"; sourceTree = SOURCE_ROOT; }; DCE5E8A02080D781005F60CE /* x-office-presentation.tvg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "x-office-presentation.tvg"; path = "img/filetypes-tvg/x-office-presentation.tvg"; sourceTree = SOURCE_ROOT; }; + DCE8AB722AE8121B00BFF393 /* branding-assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "branding-assets"; sourceTree = ""; }; DCE93FEE21FCA434000E14F2 /* libzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libzip.xcodeproj; path = external/libzip/libzip.xcodeproj; sourceTree = SOURCE_ROOT; }; DCE974B1207E3AF80069FC2B /* ThemeNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeNavigationController.swift; sourceTree = ""; }; DCE974BB207EACA60069FC2B /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; DCEA7F3F282D3B110050A3C0 /* VFSManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VFSManager.h; sourceTree = ""; }; DCEA7F40282D3B110050A3C0 /* VFSManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VFSManager.m; sourceTree = ""; }; + DCEA89812AD84D6000BFF393 /* BrandView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrandView.swift; sourceTree = ""; }; DCEAF0892808254800980B6D /* DriveListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DriveListCell.swift; sourceTree = ""; }; DCEC3DE3242F665D0076B43C /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; DCEE1C9B23A0EADD00FE8D98 /* LicenseOfferView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseOfferView.swift; sourceTree = ""; }; @@ -1660,6 +1722,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 391933B12B0B81DB007DF90B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 391933D02B0B8EC7007DF90B /* ownCloudUI.framework in Frameworks */, + 391933CF2B0B8EC3007DF90B /* ownCloudSDK.framework in Frameworks */, + 391933CE2B0B8EC0007DF90B /* ownCloudAppShared.framework in Frameworks */, + 391933CD2B0B8EBD007DF90B /* ownCloudApp.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 394A0AF622EEFC2C00603813 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1770,6 +1843,7 @@ 394A0AFA22EEFC2C00603813 /* ownCloudAppShared */, DCE4E46924C1F5610051722F /* ownCloud Share Extension */, 39DC7CCE25C2E1570001E08C /* ownCloud File Provider UI */, + 391933B72B0B81DB007DF90B /* ownCloud Action Extension */, 233BDE9D204FEFE500C06732 /* Products */, DC85573220513CC700189B9A /* Frameworks */, ); @@ -1789,6 +1863,7 @@ 394A0AF922EEFC2C00603813 /* ownCloudAppShared.framework */, DCE4E46824C1F5610051722F /* ownCloud Share Extension.appex */, 39DC7CCD25C2E1570001E08C /* ownCloud File Provider UI.appex */, + 391933B42B0B81DB007DF90B /* ownCloud Action Extension.appex */, ); name = Products; sourceTree = ""; @@ -1807,7 +1882,6 @@ DCC832D1242BB3E900153F8C /* Messages */, DC3BE0DB2077CC13002A0AC0 /* Client */, DCA35D6724CF7B6E00DBE2B0 /* Diagnostic */, - DC7DF17C205140F400189B9A /* Server List */, DCF4F1802051A91500189B9A /* Settings */, 39E42D152315286300B82AC3 /* Key Commands */, DC422448207CAED60006A2A6 /* Theming */, @@ -1914,6 +1988,16 @@ path = Client; sourceTree = ""; }; + 391933B72B0B81DB007DF90B /* ownCloud Action Extension */ = { + isa = PBXGroup; + children = ( + 391933DC2B0B9863007DF90B /* InfoPlist.strings */, + 391933D12B0B9577007DF90B /* ownCloud Action Extension.entitlements */, + 391933BF2B0B81DB007DF90B /* Info.plist */, + ); + path = "ownCloud Action Extension"; + sourceTree = ""; + }; 392DDB1224CF024C009E5406 /* Import */ = { isa = PBXGroup; children = ( @@ -1967,6 +2051,7 @@ 397754E023279EC100119FCB /* SDK Extensions */ = { isa = PBXGroup; children = ( + DCA05A612AE664F200BFF393 /* OCAction+UIAction.swift */, 399EA72525E6565900B6FF11 /* OCCore+Extension.swift */, 399EA71A25E6561D00B6FF11 /* OCShare+Extension.swift */, DC136581208223F000FC0F60 /* OCBookmark+Extension.swift */, @@ -2064,12 +2149,8 @@ 39C31F8E22F1A4F50023923D /* Theming */ = { isa = PBXGroup; children = ( - 391130102A20E3F100C22DD2 /* branding-sidebar-link-icon.png */, - 39F48A6524D847D70000E3F9 /* branding-bookmark-icon.png */, - 398393BC246D63B0001A212B /* branding-login-background.png */, - 398393BD246D63B0001A212B /* branding-login-logo.png */, - 39F48A6624D848550000E3F9 /* branding-splashscreen.png */, - 39F48A6724D848550000E3F9 /* branding-splashscreen-background.png */, + DCE8AB722AE8121B00BFF393 /* branding-assets */, + DC6F0B962AE90C4A00BFF393 /* com.owncloud.ios-app */, 3931206A2326451900E8DFBA /* Branding.plist */, ); name = Theming; @@ -2291,6 +2372,39 @@ path = "Cursor Support"; sourceTree = ""; }; + DC2323DA2AA7B5A100BFF393 /* Composer */ = { + isa = PBXGroup; + children = ( + DC2323DB2AA7B5D600BFF393 /* BookmarkComposer.swift */, + DC2323DD2AA7B6BB00BFF393 /* BookmarkComposerConfiguration.swift */, + ); + path = Composer; + sourceTree = ""; + }; + DC2323E12AA7C59900BFF393 /* Setup */ = { + isa = PBXGroup; + children = ( + DC2323DF2AA7C59400BFF393 /* BookmarkSetupViewController.swift */, + DC2323E22AA85D0300BFF393 /* BookmarkSetupStepViewController.swift */, + DC2323E72AA865DD00BFF393 /* Steps */, + ); + path = Setup; + sourceTree = ""; + }; + DC2323E72AA865DD00BFF393 /* Steps */ = { + isa = PBXGroup; + children = ( + DC28297D2AAF02A800BFF393 /* BookmarkSetupStepIntroViewController.swift */, + DC2323E52AA865A700BFF393 /* BookmarkSetupStepEnterURLViewController.swift */, + DC5908742AA87A1700BFF393 /* BookmarkSetupStepEnterUsernameViewController.swift */, + DC5908762AA87ABF00BFF393 /* BookmarkSetupStepAuthenticateViewController.swift */, + DC59087C2AA8B82200BFF393 /* BookmarkSetupStepPrepopulateViewController.swift */, + DC5908792AA87F6B00BFF393 /* BookmarkSetupStepFinishedViewController.swift */, + DC59087E2AA8D25400BFF393 /* CertificateSummaryView.swift */, + ); + path = Steps; + sourceTree = ""; + }; DC23D1D0238F38DF00423F62 /* Receipt */ = { isa = PBXGroup; children = ( @@ -2306,6 +2420,7 @@ isa = PBXGroup; children = ( DC24B2AA25BA316D005783E2 /* Branding+App.swift */, + DCEA89812AD84D6000BFF393 /* BrandView.swift */, ); path = Branding; sourceTree = ""; @@ -2468,6 +2583,7 @@ isa = PBXGroup; children = ( DC8E99E4297EEB2800594697 /* ClientLocationBarController.swift */, + DCA05A5F2AE6603E00BFF393 /* ClientLocationPopupButton.swift */, DC3F0C2429828AE300C832DB /* OCLocation+Breadcrumbs.swift */, ); path = "Location Breadcrumbs"; @@ -2691,14 +2807,6 @@ path = tools; sourceTree = ""; }; - DC7DF17C205140F400189B9A /* Server List */ = { - isa = PBXGroup; - children = ( - DC0B379320514E4700189B9A /* ServerListBookmarkCell.swift */, - ); - path = "Server List"; - sourceTree = ""; - }; DC82664028168DAA00F91F7D /* Context */ = { isa = PBXGroup; children = ( @@ -2733,6 +2841,7 @@ A56EA84D8AD331FFA604138B /* Pods_ownCloudTests.framework */, 42866B2892DC9EDC65D844E7 /* Pods_ownCloud_Screenshots_Tests.framework */, 54199937F74A129BC74DEB0A /* Pods_ownCloudScreenshotsTests.framework */, + 391933B52B0B81DB007DF90B /* UniformTypeIdentifiers.framework */, ); name = Frameworks; sourceTree = ""; @@ -2945,7 +3054,6 @@ DCB6B1EA292B7C2400D27573 /* AppRootViewController+ItemActions.swift */, DCB6B1F4292CC46B00D27573 /* AccountController+ItemActions.swift */, DCB6B20B292E428000D27573 /* AccountController+ExtraItems.swift */, - DCDA83842A9CE6C300BFF393 /* InitialSetupViewController.swift */, ); path = "App Controllers"; sourceTree = ""; @@ -3420,9 +3528,10 @@ DCF4F1612051925A00189B9A /* Bookmarks */ = { isa = PBXGroup; children = ( + DC2323DA2AA7B5A100BFF393 /* Composer */, + DC2323E12AA7C59900BFF393 /* Setup */, DC1B270B209CF34B004715E1 /* BookmarkViewController.swift */, 4CC46D202284C677009E938F /* BookmarkInfoViewController.swift */, - 394B0CFB29F958200005CBFE /* AccountSettingsProvider.swift */, ); path = Bookmarks; sourceTree = ""; @@ -3692,12 +3801,13 @@ buildPhases = ( 23813A32205286E100DB9488 /* Run SwiftLint */, 233BDE98204FEFE500C06732 /* Sources */, + DC85573420513CCC00189B9A /* Copy Frameworks */, 233BDE99204FEFE500C06732 /* Frameworks */, 233BDE9A204FEFE500C06732 /* Resources */, - DC85573420513CCC00189B9A /* Copy Frameworks */, DC63207821FCA6A4007EC0A8 /* Copy libzip license */, - 23DFDCF120AEEC77003BD16B /* Update LastGitCommit key in Info.plist */, + DC6F0B952AE9014B00BFF393 /* Copy branding assets */, DCC6567020C9B7E400110A97 /* Embed Foundation Extensions */, + 23DFDCF120AEEC77003BD16B /* Update LastGitCommit key in Info.plist */, ); buildRules = ( ); @@ -3713,6 +3823,7 @@ DCE4E47124C1F5610051722F /* PBXTargetDependency */, DC0491AA258CAF9800DEDC27 /* PBXTargetDependency */, 39DC7CD625C2E1570001E08C /* PBXTargetDependency */, + 391933C12B0B81DB007DF90B /* PBXTargetDependency */, ); name = ownCloud; packageProductDependencies = ( @@ -3746,6 +3857,27 @@ productReference = 233BDEB0204FEFE500C06732 /* ownCloudTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 391933B32B0B81DB007DF90B /* ownCloud Action Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 391933C32B0B81DC007DF90B /* Build configuration list for PBXNativeTarget "ownCloud Action Extension" */; + buildPhases = ( + 391933B02B0B81DB007DF90B /* Sources */, + 391933B12B0B81DB007DF90B /* Frameworks */, + 391933B22B0B81DB007DF90B /* Resources */, + 39CBC5422B44633800E81D24 /* Copy branding asset action extension icon */, + ); + buildRules = ( + ); + dependencies = ( + 391933CC2B0B8EA9007DF90B /* PBXTargetDependency */, + 391933CA2B0B8EA5007DF90B /* PBXTargetDependency */, + 391933C82B0B8EA2007DF90B /* PBXTargetDependency */, + ); + name = "ownCloud Action Extension"; + productName = "ownCloud Action Extension"; + productReference = 391933B42B0B81DB007DF90B /* ownCloud Action Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 394A0AF822EEFC2C00603813 /* ownCloudAppShared */ = { isa = PBXNativeTarget; buildConfigurationList = 394A0B0222EEFC2C00603813 /* Build configuration list for PBXNativeTarget "ownCloudAppShared" */; @@ -3763,6 +3895,7 @@ DC0491D8258CB00000DEDC27 /* PBXTargetDependency */, DCDC0ACE23CD185F00DFE36D /* PBXTargetDependency */, 394A0B0C22EEFD2800603813 /* PBXTargetDependency */, + DC2A6C762AC6BE0100BFF393 /* PBXTargetDependency */, ); name = ownCloudAppShared; packageProductDependencies = ( @@ -3922,7 +4055,7 @@ 233BDE94204FEFE500C06732 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1150; + LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1420; ORGANIZATIONNAME = "ownCloud GmbH"; TargetAttributes = { @@ -3951,6 +4084,9 @@ ProvisioningStyle = Automatic; TestTargetID = 233BDE9B204FEFE500C06732; }; + 391933B32B0B81DB007DF90B = { + CreatedOnToolsVersion = 15.0; + }; 394A0AF822EEFC2C00603813 = { CreatedOnToolsVersion = 11.0; LastSwiftMigration = 1430; @@ -4048,6 +4184,7 @@ DCC0855B2293F1FD008CC05C /* ownCloudApp */, 394A0AF822EEFC2C00603813 /* ownCloudAppShared */, DCC085632293F1FD008CC05C /* ownCloudAppTests */, + 391933B32B0B81DB007DF90B /* ownCloud Action Extension */, ); }; /* End PBXProject section */ @@ -4110,19 +4247,13 @@ buildActionMask = 2147483647; files = ( 39DF77D524EA854C0066E8F0 /* LaunchScreen.storyboard in Resources */, - 3923790524EBD1A5006E86DE /* branding-splashscreen-background.png in Resources */, - 398393BE246D63B0001A212B /* branding-login-background.png in Resources */, 3968C883239C54AD00AC28AC /* ReleaseNotes.plist in Resources */, - 398393BF246D63B0001A212B /* branding-login-logo.png in Resources */, 59D4895220C83F2E00369C2E /* InfoPlist.strings in Resources */, - 392378FF24EBD1A1006E86DE /* branding-splashscreen.png in Resources */, 593A821120C7D4C5000E2A90 /* Localizable.strings in Resources */, - 391130112A20E3F200C22DD2 /* branding-sidebar-link-icon.png in Resources */, DCE684F6241BD4E800799C30 /* Branding.plist in Resources */, DC9BFBB320A19AF4007064B5 /* doc in Resources */, 233BDEA7204FEFE500C06732 /* Assets.xcassets in Resources */, DC6C68362574FD0400E46BD4 /* PLCrashReporter.LICENSE in Resources */, - 39F48A6A24D89D7E0000E3F9 /* branding-bookmark-icon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4134,6 +4265,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 391933B22B0B81DB007DF90B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 391933DA2B0B9863007DF90B /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 394A0AF722EEFC2C00603813 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4251,6 +4390,27 @@ shellPath = /bin/sh; shellScript = "LASTGITCOMMIT=$(git rev-parse --short HEAD)\ndefaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"LastGitCommit\" \"${LASTGITCOMMIT}\"\n\nGITTAGS=$(git describe --tags)\ndefaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"GitTags\" \"${GITTAGS}\"\n\nGITBRANCH=$(git branch --show-current)\ndefaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"GitBranch\" \"${GITBRANCH}\"\n\nBUILDDATE=$(date)\ndefaults write \"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH%.*}\" \"BuildDate\" \"${BUILDDATE}\"\n"; }; + 39CBC5422B44633800E81D24 /* Copy branding asset action extension icon */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets/branding-action-extension-icon.png", + "${PROJECT_DIR}/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-action-extension-icon.png", + ); + name = "Copy branding asset action extension icon"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-action-extension-icon.png", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/bin/bash\n\n# Determine branding assets folder to use\nASSETS_SOURCE_FOLDER=\"${PROJECT_DIR}/ownCloud/Resources/Theming/com.owncloud.ios-app/\"\n\nASSETS_FILE_COUNT=$(ls -A \"${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets/\"branding-action-extension-icon.png | wc -l)\necho \"File count in branding-assets:\" ${ASSETS_FILE_COUNT}\n\nif [ -e \"${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets/\" ]; then\n if [ $ASSETS_FILE_COUNT -ne 0 ]; then\n # Copy branding-*.png files from branding-assets\n ASSETS_SOURCE_FOLDER=\"${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets/\"\n fi\nfi\n\n\nICON_FILE_COUNT=$(ls -A ${ASSETS_SOURCE_FOLDER}branding-action-extension-icon.png | wc -l)\n\necho \"Icon count in assets source folder:\" ${ICON_FILE_COUNT}\n\nif [ $ICON_FILE_COUNT -ne 0 ]; then\n # Copy branding-action-extension-icon.png assets\n echo \"Copying branding-action-extension-icon.png files from\" ${ASSETS_SOURCE_FOLDER}\n echo cp ${ASSETS_SOURCE_FOLDER}branding-action-extension-icon.png \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/\"\n cp ${ASSETS_SOURCE_FOLDER}branding-action-extension-icon.png \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-action-extension-icon.png\"\nfi\n"; + }; DC049259258CB33600DEDC27 /* Copy PocketSVG license (inactive) */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -4276,9 +4436,9 @@ files = ( ); inputFileListPaths = ( - "${PROJECT_DIR}/external/libzip/LICENSE", ); inputPaths = ( + "${PROJECT_DIR}/external/libzip/LICENSE", ); name = "Copy libzip license"; outputFileListPaths = ( @@ -4290,6 +4450,26 @@ shellPath = /bin/sh; shellScript = "echo \"${PROJECT_DIR}/external/libzip/LICENSE\" \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/libzip.LICENSE\"\ncp \"${PROJECT_DIR}/external/libzip/LICENSE\" \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/libzip.LICENSE\"\n"; }; + DC6F0B952AE9014B00BFF393 /* Copy branding assets */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets-input.xcfilelist", + ); + inputPaths = ( + ); + name = "Copy branding assets"; + outputFileListPaths = ( + "${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets-output.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/bash; + shellScript = "#!/bin/bash\n\n# Determine branding assets folder to use\nASSETS_SOURCE_FOLDER=\"${PROJECT_DIR}/ownCloud/Resources/Theming/com.owncloud.ios-app/\"\n\nASSETS_FILE_COUNT=$(ls -A \"${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets/\"branding-*.png | wc -l)\necho \"File count in branding-assets:\" ${ASSETS_FILE_COUNT}\n\nif [ -e \"${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets/\" ]; then\n\tif [ $ASSETS_FILE_COUNT -ne 0 ]; then\n\t\t# Copy branding-*.png files from branding-assets\n\t\tASSETS_SOURCE_FOLDER=\"${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets/\"\n\tfi\nfi\n\n# Copy branding-*.png assets\necho \"Copying branding-*.png files from\" ${ASSETS_SOURCE_FOLDER}\necho cp ${ASSETS_SOURCE_FOLDER}branding-*.png \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/\"\ncp ${ASSETS_SOURCE_FOLDER}branding-*.png \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/\"\n\n# Remove assets from the assets folder that aren't used by the app from the build product\nif [ -e \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-icon.png\" ]; then\n rm \"${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-icon.png\"\nfi\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -4298,6 +4478,7 @@ buildActionMask = 2147483647; files = ( DC3DEC7B22AFA1F000F3352D /* DownloadItemsHUDViewController.swift in Sources */, + DC28297E2AAF02A800BFF393 /* BookmarkSetupStepIntroViewController.swift in Sources */, 39EF06B325D6C3FC001E1E19 /* PresentationModeAction.swift in Sources */, DC680576212DF548006C3B1F /* CertificateManagementViewController.swift in Sources */, 4CB8ADE322DF6BA700F1FEBC /* PHAsset+Upload.swift in Sources */, @@ -4308,18 +4489,23 @@ 6E4F1734217749910049A71B /* ImageDisplayViewController.swift in Sources */, DC8EB271239308E5009148F9 /* LicenseOffersViewController.swift in Sources */, 025FC742247D5004009307A7 /* MediaUploadOperation.swift in Sources */, + DC59087A2AA87F6B00BFF393 /* BookmarkSetupStepFinishedViewController.swift in Sources */, 4C11EE5B22E88D4200B84869 /* InstantMediaUploadTaskExtension.swift in Sources */, DC6CF7FB219446050013B9F9 /* LogSettingsViewController.swift in Sources */, 39878B7421FB1DE800DBF693 /* UINavigationController+Extension.swift in Sources */, + DC2323DC2AA7B5D600BFF393 /* BookmarkComposer.swift in Sources */, DC82D6FA23171339001551C5 /* ScanAction.swift in Sources */, DCC3700724D466D2008B0DEB /* DiagnosticManager.swift in Sources */, 23EC775D2137FB6B0032D4E6 /* WebViewDisplayViewController.swift in Sources */, 4C464BF12187AF1500D30602 /* PDFTocTableViewCell.swift in Sources */, DCE4E43E24C19C3E0051722F /* Action+UserInterface.swift in Sources */, + DC2323DE2AA7B6BB00BFF393 /* BookmarkComposerConfiguration.swift in Sources */, DC1B270C209CF34B004715E1 /* BookmarkViewController.swift in Sources */, 025FC745247EF0F1009307A7 /* BackgroundUploadsSettingsSection.swift in Sources */, DC63208321FCAC1E007EC0A8 /* ClientActivityViewController.swift in Sources */, DC9C1AEC247C76470067895A /* MessageGroupCell.swift in Sources */, + DC2323E32AA85D0300BFF393 /* BookmarkSetupStepViewController.swift in Sources */, + DC5908772AA87ABF00BFF393 /* BookmarkSetupStepAuthenticateViewController.swift in Sources */, 4C464BF62187AF1500D30602 /* PDFTocItem.swift in Sources */, 6E3A103E219D5BBA00F90C96 /* RenameAction.swift in Sources */, DCDC208F23994DFB003CFF5B /* LicenseTransactionsViewController.swift in Sources */, @@ -4337,9 +4523,11 @@ DC0030CB2350B75000BB8570 /* ScanViewController.swift in Sources */, 4C464BF42187AF1500D30602 /* PDFSearchTableViewCell.swift in Sources */, DCD1300A23A191C000255779 /* LicenseOfferButton.swift in Sources */, + DC2323E02AA7C59400BFF393 /* BookmarkSetupViewController.swift in Sources */, DCFEF90926EFA45A001DC7A4 /* VendorServices+App.swift in Sources */, 4C9BFA2323158C3F0059CA3E /* PreviewViewController.swift in Sources */, 399698ED260A3CEE00E5AEBA /* ImportPasteboardAction.swift in Sources */, + DC59087F2AA8D25400BFF393 /* CertificateSummaryView.swift in Sources */, 4C3E17DB234DBF9A000D7BA8 /* PendingMediaUploadTaskExtension.swift in Sources */, DCC832DE242C0C3700153F8C /* DisplaySleepPreventer.swift in Sources */, 6E586CFC2199A72600F680C4 /* OpenInAction.swift in Sources */, @@ -4348,8 +4536,8 @@ 4C1561E8222321E0009C4EF3 /* PhotoSelectionViewController.swift in Sources */, 39CC8AE6228C12100020253B /* Array+Extension.swift in Sources */, DCE28F602433683700879DEC /* ClientSessionManager.swift in Sources */, + DC5908752AA87A1700BFF393 /* BookmarkSetupStepEnterUsernameViewController.swift in Sources */, 3968C881239C54AC00AC28AC /* ReleaseNotesHostViewController.swift in Sources */, - 394B0CFC29F958200005CBFE /* AccountSettingsProvider.swift in Sources */, DC6428D02081406800493A01 /* CollapsibleProgressBar.swift in Sources */, 39CD755423D8392D00193950 /* EditDocumentViewController.swift in Sources */, 025F063A24AA18C7009D8FC5 /* ImageMetadataViewController.swift in Sources */, @@ -4416,10 +4604,11 @@ DC6C0A4929239E560045FF2A /* AppRootViewController.swift in Sources */, 02DC7C9024CB354800DCB2C6 /* ProPhotoUploadSettingsSection.swift in Sources */, DCC832F6242CC5F700153F8C /* CardIssueMessagePresenter.swift in Sources */, + DC59087D2AA8B82200BFF393 /* BookmarkSetupStepPrepopulateViewController.swift in Sources */, DCD1301123A23F4E00255779 /* OCLicenseManager+AppStore.swift in Sources */, DC62514C225D254500736874 /* UploadBaseAction.swift in Sources */, 4C6B78102226B83300C5F3DB /* PhotoAlbumTableViewController.swift in Sources */, - DCDA83852A9CE6C300BFF393 /* InitialSetupViewController.swift in Sources */, + DC2323E62AA865A700BFF393 /* BookmarkSetupStepEnterURLViewController.swift in Sources */, 23EC77592137F3DD0032D4E6 /* DisplayExtension.swift in Sources */, DCDF58B323CE82E100080BEB /* LicenseInAppPurchaseFeatureView.swift in Sources */, 02D4C82A255208E60000E299 /* PDFSearchResultsView.swift in Sources */, @@ -4443,6 +4632,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 391933B02B0B81DB007DF90B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 391933C62B0B8E5A007DF90B /* ShareExtensionViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 394A0AF522EEFC2C00603813 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4529,6 +4726,7 @@ DC46F69028DCA1B8008280CA /* SavedSearchCell.swift in Sources */, DC66A9F4279EEBF900792AC8 /* ThemeView.swift in Sources */, DCDE444D2A36F56000BFF393 /* AppStateActionOpenItem.swift in Sources */, + DCEA89822AD84D6000BFF393 /* BrandView.swift in Sources */, DC60F2AA29802D5800905EC8 /* NavigationContentItem.swift in Sources */, DC0A356F24C0E42700FB58FC /* StaticTableViewController.swift in Sources */, DC62F569292504510095BB5D /* AccountConnectionPool.swift in Sources */, @@ -4613,6 +4811,7 @@ DCB1B89F29C7378200BFF393 /* ThemeCSS.swift in Sources */, DC0A357C24C0E43C00FB58FC /* ThemeCollection.swift in Sources */, DC28F828294BB5ED00AC4013 /* SortedItemDataSource.swift in Sources */, + DCA05A602AE6603E00BFF393 /* ClientLocationPopupButton.swift in Sources */, DC298C992934D3F8009FA87F /* AlertViewController.swift in Sources */, DC8E99DC297E79E900594697 /* BrowserNavigationHistory.swift in Sources */, DCBAEAE829A568D500BFF393 /* TitleSupplementaryCell.swift in Sources */, @@ -4654,6 +4853,7 @@ 3912208223436EB80026C290 /* SortMethod.swift in Sources */, DCE4E43F24C19D370051722F /* UIAlertController+OCIssue.swift in Sources */, DC298CA929362523009FA87F /* ClientLocationPicker.swift in Sources */, + DCA05A622AE664F200BFF393 /* OCAction+UIAction.swift in Sources */, DC0A359524C0E5F900FB58FC /* UIImage+Extension.swift in Sources */, DC89EA6B29959BD200BFF393 /* AppStateActionRestoreNavigationBookmark.swift in Sources */, DC66A9F8279F467200792AC8 /* UIKeyCommand+Extension.swift in Sources */, @@ -4815,6 +5015,26 @@ target = 233BDE9B204FEFE500C06732 /* ownCloud */; targetProxy = 233BDEB1204FEFE500C06732 /* PBXContainerItemProxy */; }; + 391933C12B0B81DB007DF90B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 391933B32B0B81DB007DF90B /* ownCloud Action Extension */; + targetProxy = 391933C02B0B81DB007DF90B /* PBXContainerItemProxy */; + }; + 391933C82B0B8EA2007DF90B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DCC0855B2293F1FD008CC05C /* ownCloudApp */; + targetProxy = 391933C72B0B8EA2007DF90B /* PBXContainerItemProxy */; + }; + 391933CA2B0B8EA5007DF90B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 394A0AF822EEFC2C00603813 /* ownCloudAppShared */; + targetProxy = 391933C92B0B8EA5007DF90B /* PBXContainerItemProxy */; + }; + 391933CC2B0B8EA9007DF90B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ownCloudSDK; + targetProxy = 391933CB2B0B8EA9007DF90B /* PBXContainerItemProxy */; + }; 394A0AFF22EEFC2C00603813 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 394A0AF822EEFC2C00603813 /* ownCloudAppShared */; @@ -4889,6 +5109,11 @@ name = ownCloudSDK; targetProxy = DC27A19220CAA0C6008ACB6C /* PBXContainerItemProxy */; }; + DC2A6C762AC6BE0100BFF393 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ownCloudUI; + targetProxy = DC2A6C752AC6BE0100BFF393 /* PBXContainerItemProxy */; + }; DC3BE0CF2077BC52002A0AC0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = ownCloudSDK; @@ -5010,6 +5235,15 @@ name = Intents.intentdefinition; sourceTree = ""; }; + 391933DC2B0B9863007DF90B /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 391933DB2B0B9863007DF90B /* en */, + 391933DD2B0B9871007DF90B /* de */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; 39534BC924EA903200AD7907 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -5137,8 +5371,8 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 12.0.3; - APP_VERSION = 271; + APP_SHORT_VERSION = 12.1; + APP_VERSION = 288; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -5207,8 +5441,8 @@ APP_BUILD_FLAGS = "$(inherited)"; APP_BUILD_FLAGS_SWIFT = "$(APP_BUILD_FLAGS)"; APP_PRODUCT_NAME = ownCloud; - APP_SHORT_VERSION = 12.0.3; - APP_VERSION = 271; + APP_SHORT_VERSION = 12.1; + APP_VERSION = 288; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -5368,6 +5602,86 @@ }; name = Release; }; + 391933C42B0B81DC007DF90B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CODE_SIGN_ENTITLEMENTS = "ownCloud Action Extension/ownCloud Action Extension.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(APP_VERSION)"; + DEVELOPMENT_TEAM = 4AP2STM4H5; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "ownCloud Action Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "ownCloud Action Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 ownCloud GmbH. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = "${APP_SHORT_VERSION}"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.owncloud.ios-app.ownCloud-Action-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 391933C52B0B81DC007DF90B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CODE_SIGN_ENTITLEMENTS = "ownCloud Action Extension/ownCloud Action Extension.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(APP_VERSION)"; + DEVELOPMENT_TEAM = 4AP2STM4H5; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "ownCloud Action Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "ownCloud Action Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 ownCloud GmbH. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = "${APP_SHORT_VERSION}"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.owncloud.ios-app.ownCloud-Action-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 394A0B0322EEFC2C00603813 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5830,6 +6144,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 391933C32B0B81DC007DF90B /* Build configuration list for PBXNativeTarget "ownCloud Action Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 391933C42B0B81DC007DF90B /* Debug */, + 391933C52B0B81DC007DF90B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 394A0B0222EEFC2C00603813 /* Build configuration list for PBXNativeTarget "ownCloudAppShared" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud File Provider.xcscheme b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud File Provider.xcscheme index 69ff7ee8c..c0eaa4db2 100644 --- a/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud File Provider.xcscheme +++ b/ownCloud.xcodeproj/xcshareddata/xcschemes/ownCloud File Provider.xcscheme @@ -109,7 +109,7 @@ + RemotePath = "/Library/Developer/CoreSimulator/Volumes/iOS_21A342/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.0.simruntime/Contents/Resources/RuntimeRoot/Applications/Files.app"> @@ -142,7 +142,7 @@ isEnabled = "NO"> @@ -151,7 +151,29 @@ value = "[share-extension,open-with,file-provider]" isEnabled = "NO"> + + + + + + + + + + + isEnabled = "YES"> - - + + + + + + + + + + + + + + + + UICollectionViewDropProposal? { + if session.localDragSession == nil { + return nil + } + + return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + + public func performDropOperation(of items: [UIDragItem], with context: ClientContext?, handlingCompletion: @escaping (Bool) -> Void) { + handlingCompletion(false) + } +} diff --git a/ownCloud/App Controllers/AppRootViewController.swift b/ownCloud/App Controllers/AppRootViewController.swift index fbce0e7c1..2c37e2468 100644 --- a/ownCloud/App Controllers/AppRootViewController.swift +++ b/ownCloud/App Controllers/AppRootViewController.swift @@ -104,7 +104,7 @@ open class AppRootViewController: EmbeddingViewController, BrowserNavigationView // Build sidebar sidebarViewController = ClientSidebarViewController(context: rootContext!, controllerConfiguration: controllerConfiguration) - sidebarViewController?.addToolbarItems() + sidebarViewController?.addToolbarItems(addAccount: Branding.shared.canAddAccount) leftNavigationController = ThemeNavigationController(rootViewController: sidebarViewController!) leftNavigationController?.cssSelectors = [ .sidebar ] @@ -121,7 +121,9 @@ open class AppRootViewController: EmbeddingViewController, BrowserNavigationView noBookmarkCondition = DataSourceCondition(.empty, with: OCBookmarkManager.shared.bookmarksDatasource, initial: true, action: { [weak self] condition in if condition.fulfilled == true { // No account available - self?.contentViewController = InitialSetupViewController() + let configuration = BookmarkComposerConfiguration.newBookmarkConfiguration + configuration.hasIntro = true + self?.contentViewController = BookmarkSetupViewController(configuration: configuration) } else { // Account already available self?.contentViewController = self?.contentBrowserController @@ -160,6 +162,23 @@ open class AppRootViewController: EmbeddingViewController, BrowserNavigationView ClientSessionManager.shared.remove(delegate: self) } + // MARK: - Interface orientations + open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + if let contentViewController { + return contentViewController.supportedInterfaceOrientations + } + + return super.supportedInterfaceOrientations + } + + open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + if let contentViewController { + return contentViewController.preferredInterfaceOrientationForPresentation + } + + return super.preferredInterfaceOrientationForPresentation + } + // MARK: - Status Bar style open override var childForStatusBarStyle: UIViewController? { return contentViewController @@ -168,6 +187,9 @@ open class AppRootViewController: EmbeddingViewController, BrowserNavigationView open override var contentViewController: UIViewController? { didSet { setNeedsStatusBarAppearanceUpdate() + if #available(iOS 16, *) { + setNeedsUpdateOfSupportedInterfaceOrientations() + } } } @@ -384,7 +406,9 @@ extension ClientSidebarViewController { // MARK: - Open settings @IBAction func settings() { - self.present(ThemeNavigationController(rootViewController: SettingsViewController()), animated: true) + let navigationViewController = ThemeNavigationController(rootViewController: SettingsViewController()) + navigationViewController.modalPresentationStyle = .fullScreen + present(navigationViewController, animated: true) } // MARK: - Add account diff --git a/ownCloud/App Controllers/InitialSetupViewController.swift b/ownCloud/App Controllers/InitialSetupViewController.swift deleted file mode 100644 index 66b6f2533..000000000 --- a/ownCloud/App Controllers/InitialSetupViewController.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// InitialSetupViewController.swift -// ownCloud -// -// Created by Felix Schwarz on 28.08.23. -// Copyright © 2023 ownCloud GmbH. All rights reserved. -// - -/* - * Copyright (C) 2023, ownCloud GmbH. - * - * This code is covered by the GNU Public License Version 3. - * - * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ - * You should have received a copy of this license along with this program. If not, see . - * - */ - -import UIKit -import ownCloudApp -import ownCloudAppShared - -class InitialSetupViewController: UIViewController { - override var preferredStatusBarStyle : UIStatusBarStyle { - return Theme.shared.activeCollection.css.getStatusBarStyle(for: self) ?? .default - } - - override func loadView() { - cssSelectors = [.modal, .welcome] - - var addAccountTitle = "Add account".localized - if !VendorServices.shared.canAddAccount { - addAccountTitle = "Login".localized - } - - let messageView = ComposedMessageView.infoBox(additionalElements: [ - .image(AccountSettingsProvider.shared.logo, size: CGSize(width: 128, height: 128), cssSelectors: [.icon]), - .title(String(format: "Welcome to %@".localized, VendorServices.shared.appName), alignment: .centered, cssSelectors: [.title], insets: NSDirectionalEdgeInsets(top: 25, leading: 0, bottom: 25, trailing: 0)), - .button(addAccountTitle, action: UIAction(handler: { [weak self] action in - if let self = self { - BookmarkViewController.showBookmarkUI(on: self, attemptLoginOnSuccess: true) - } - }), image: UIImage(systemName: "plus.circle"), cssSelectors: [.welcome]), - .button("Settings".localized ,action: UIAction(handler: { [weak self] action in - if let self = self { - self.present(ThemeNavigationController(rootViewController: SettingsViewController()), animated: true) - } - }), image: UIImage(systemName: "gearshape"), cssSelectors: [.welcome]) - ]) - messageView.elementInsets = NSDirectionalEdgeInsets(top: 25, leading: 50, bottom: 50, trailing: 50) - - let rootView = ThemeCSSView(withSelectors: []) - - if let image = Branding.shared.brandedImageNamed(.loginBackground) { - messageView.isOpaque = false - let backgroundImageView = UIImageView(image: image) - backgroundImageView.contentMode = .scaleAspectFill - rootView.embed(toFillWith: backgroundImageView) - } - - rootView.embed(centered: messageView, minimumInsets: NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)) - - view = rootView - } -} diff --git a/ownCloud/Bookmarks/AccountSettingsProvider.swift b/ownCloud/Bookmarks/AccountSettingsProvider.swift deleted file mode 100644 index 03f30a99c..000000000 --- a/ownCloud/Bookmarks/AccountSettingsProvider.swift +++ /dev/null @@ -1,129 +0,0 @@ -// -// AccountSettingsProvider.swift -// ownCloud -// -// Created by Matthias Hühne on 26.04.23. -// Copyright © 2023 ownCloud GmbH. All rights reserved. -// - -/* - * Copyright (C) 2018, ownCloud GmbH. - * - * This code is covered by the GNU Public License Version 3. - * - * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ - * You should have received a copy of this license along with this program. If not, see . - * - */ - -import UIKit -import ownCloudSDK -import ownCloudApp -import ownCloudAppShared - -class AccountSettingsProvider: NSObject { - - static public var shared : AccountSettingsProvider = { - return AccountSettingsProvider() - }() - - public var defaultBookmarkName: String? { - if let name = Branding.shared.profileBookmarkName { - return name - } else if let name = self.classSetting(forOCClassSettingsKey: .bookmarkDefaultName) as? String { - return name - } - - return nil - } - - public var defaultURL: URL? { - if let url = Branding.shared.profileURL { - return url - } else if let urlString = self.classSetting(forOCClassSettingsKey: .bookmarkDefaultURL) as? String { - return URL(string: urlString) - } - - return nil - } - - public var URLEditable: Bool { - if let url = Branding.shared.profileAllowUrlConfiguration { - return url - } else if let value = self.classSetting(forOCClassSettingsKey: .bookmarkURLEditable) as? Bool { - return value - } - - return true - } - - public var profileOpenHelpMessage: String? { - return Branding.shared.profileOpenHelpMessage - } - - public var profileHelpButtonLabel: String? { - return Branding.shared.profileHelpButtonLabel - } - - public var profileHelpURL: URL? { - return Branding.shared.profileHelpURL - } - - var logo: UIImage { - if Branding.shared.isBranded, let image = Branding.shared.brandedImageNamed(.loginLogo) { - return image - } - - return UIImage(named: "branding-bookmark-icon")! - } -} - -// MARK: - OCClassSettings support -extension OCClassSettingsIdentifier { - static let accountSettings = OCClassSettingsIdentifier("account-settings") -} - -extension OCClassSettingsKey { - static let bookmarkDefaultName = OCClassSettingsKey("default-name") - static let bookmarkDefaultURL = OCClassSettingsKey("default-url") - static let bookmarkURLEditable = OCClassSettingsKey("url-editable") -} - -extension AccountSettingsProvider : OCClassSettingsSupport { - public static let classSettingsIdentifier : OCClassSettingsIdentifier = .accountSettings - - public static func defaultSettings(forIdentifier identifier: OCClassSettingsIdentifier) -> [OCClassSettingsKey : Any]? { - if identifier == .bookmark { - return [ - .bookmarkURLEditable : true - ] - } - - return nil - } - - public static func classSettingsMetadata() -> [OCClassSettingsKey : [OCClassSettingsMetadataKey : Any]]? { - return [ - .bookmarkDefaultName : [ - .type : OCClassSettingsMetadataType.string, - .description : "The default name for the creation of new bookmarks.", - .category : "Bookmarks", - .status : OCClassSettingsKeyStatus.supported - ], - - .bookmarkDefaultURL : [ - .type : OCClassSettingsMetadataType.string, - .description : "The default URL for the creation of new bookmarks.", - .category : "Bookmarks", - .status : OCClassSettingsKeyStatus.supported - ], - - .bookmarkURLEditable : [ - .type : OCClassSettingsMetadataType.boolean, - .description : "Controls whether the server URL in the text field during the creation of new bookmarks can be changed.", - .category : "Bookmarks", - .status : OCClassSettingsKeyStatus.supported - ] - ] - } -} diff --git a/ownCloud/Bookmarks/BookmarkViewController.swift b/ownCloud/Bookmarks/BookmarkViewController.swift index 216fd41be..e907dff31 100644 --- a/ownCloud/Bookmarks/BookmarkViewController.swift +++ b/ownCloud/Bookmarks/BookmarkViewController.swift @@ -273,7 +273,7 @@ class BookmarkViewController: StaticTableViewController { self.navigationItem.rightBarButtonItem = continueBarButtonItem // Support for bookmark default name - if let defaultNameString = AccountSettingsProvider.shared.defaultBookmarkName { + if let defaultNameString = Branding.shared.profileBookmarkName { self.bookmark?.name = defaultNameString if bookmark != nil { @@ -282,7 +282,7 @@ class BookmarkViewController: StaticTableViewController { } // Support for bookmark default URL - if let defaultURL = AccountSettingsProvider.shared.defaultURL { + if let defaultURL = Branding.shared.profileURL { self.bookmark?.url = defaultURL if bookmark != nil { @@ -290,13 +290,13 @@ class BookmarkViewController: StaticTableViewController { } } - if let url = AccountSettingsProvider.shared.profileHelpURL, let title = AccountSettingsProvider.shared.profileHelpButtonLabel { + if let url = Branding.shared.profileHelpURL, let title = Branding.shared.profileHelpButtonLabel { let imageView = UIImageView(image: UIImage(systemName: "questionmark.circle")!) helpButtonRow = StaticTableViewRow(rowWithAction: { staticRow, sender in UIApplication.shared.open(url) }, title: title, alignment: .center, accessoryView: imageView) - helpSection = StaticTableViewSection(headerTitle: "Help".localized, footerTitle: AccountSettingsProvider.shared.profileOpenHelpMessage, identifier: "section-help", rows: [ helpButtonRow! ]) + helpSection = StaticTableViewSection(headerTitle: "Help".localized, footerTitle: Branding.shared.profileOpenHelpMessage, identifier: "section-help", rows: [ helpButtonRow! ]) } case .edit: @@ -319,7 +319,7 @@ class BookmarkViewController: StaticTableViewController { } // Support for bookmark URL editable - if AccountSettingsProvider.shared.URLEditable == false { + if Branding.shared.profileAllowUrlConfiguration == false { self.urlRow?.enabled = false let vectorImageView = VectorImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) @@ -340,7 +340,7 @@ class BookmarkViewController: StaticTableViewController { } let logoAndAppNameView = ComposedMessageView.infoBox(additionalElements: [ - .image(AccountSettingsProvider.shared.logo, size: CGSize(width: 64, height: 64), cssSelectors: [.icon]), + .image(Branding.shared.brandedImageNamed(.brandLogo) ?? UIImage(systemName: "globe")!, size: CGSize(width: 64, height: 64), cssSelectors: [.icon]), .title(VendorServices.shared.appName, alignment: .centered, cssSelectors: [.title]) ]) @@ -348,7 +348,7 @@ class BookmarkViewController: StaticTableViewController { logoAndAppNameView.backgroundInsets = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 0, trailing: 20) logoAndAppNameView.elementInsets = NSDirectionalEdgeInsets(top: 30, leading: 20, bottom: 10, trailing: 20) - (logoAndAppNameView.backgroundView as? RoundCornerBackgroundView)?.fillImage = Branding.shared.brandedImageNamed(.loginBackground) + (logoAndAppNameView.backgroundView as? RoundCornerBackgroundView)?.fillImage = Branding.shared.brandedImageNamed(.brandBackground) ?? Branding.shared.brandedImageNamed(.legacyBrandBackground) self.tableView.tableHeaderView = logoAndAppNameView self.tableView.layoutTableHeaderView() @@ -1108,39 +1108,55 @@ class BookmarkViewController: StaticTableViewController { // MARK: - Convenience for presentation extension BookmarkViewController { static func showBookmarkUI(on hostViewController: UIViewController, edit bookmark: OCBookmark? = nil, performContinue: Bool = false, attemptLoginOnSuccess: Bool = false, autosolveErrorOnSuccess: NSError? = nil, removeAuthDataFromCopy: Bool = true) { - var editBookmark = bookmark + if bookmark != nil { + var editBookmark = bookmark - if let bookmark { - // Retrieve latest version of bookmark from OCBookmarkManager - if let latestStoredBookmarkVersion = OCBookmarkManager.shared.bookmark(forUUIDString: bookmark.uuid.uuidString) { - editBookmark = latestStoredBookmarkVersion + if let bookmark { + // Retrieve latest version of bookmark from OCBookmarkManager + if let latestStoredBookmarkVersion = OCBookmarkManager.shared.bookmark(forUUIDString: bookmark.uuid.uuidString) { + editBookmark = latestStoredBookmarkVersion + } } - } - let bookmarkViewController : BookmarkViewController = BookmarkViewController(editBookmark, removeAuthDataFromCopy: removeAuthDataFromCopy) - bookmarkViewController.userActionCompletionHandler = { (bookmark, success) in - if success, let bookmark = bookmark { - if let error = autosolveErrorOnSuccess as Error? { - OCMessageQueue.global.resolveIssues(forError: error, forBookmarkUUID: bookmark.uuid) - } + let bookmarkViewController : BookmarkViewController = BookmarkViewController(editBookmark, removeAuthDataFromCopy: removeAuthDataFromCopy) + bookmarkViewController.userActionCompletionHandler = { (bookmark, success) in + if success, let bookmark = bookmark { + if let error = autosolveErrorOnSuccess as Error? { + OCMessageQueue.global.resolveIssues(forError: error, forBookmarkUUID: bookmark.uuid) + } - if attemptLoginOnSuccess { - AccountConnectionPool.shared.connection(for: bookmark)?.connect() + if attemptLoginOnSuccess { + AccountConnectionPool.shared.connection(for: bookmark)?.connect() + } } } - } - let navigationController : ThemeNavigationController = ThemeNavigationController(rootViewController: bookmarkViewController) - navigationController.isModalInPresentation = true + let navigationController : ThemeNavigationController = ThemeNavigationController(rootViewController: bookmarkViewController) + navigationController.isModalInPresentation = true - hostViewController.present(navigationController, animated: true, completion: { - OnMainThread { - if performContinue { - bookmarkViewController.showedOAuthInfoHeader = true // needed for HTTP+OAuth2 connections to really continue on .handleContinue() call - bookmarkViewController.handleContinue() + hostViewController.present(navigationController, animated: true, completion: { + OnMainThread { + if performContinue { + bookmarkViewController.showedOAuthInfoHeader = true // needed for HTTP+OAuth2 connections to really continue on .handleContinue() call + bookmarkViewController.handleContinue() + } } - } - }) + }) + } else { + let setupViewController = BookmarkSetupViewController(configuration: .newBookmarkConfiguration, cancelHandler: { + hostViewController.dismiss(animated: true) + }, doneHandler: { bookmark in + hostViewController.dismiss(animated: true) + if attemptLoginOnSuccess, let bookmark { + AccountConnectionPool.shared.connection(for: bookmark)?.connect() + } + }) + setupViewController.navigationItem.titleLabelText = "Add account".localized + + let navigationViewController = ThemeNavigationController(rootViewController: setupViewController) + navigationViewController.modalPresentationStyle = .fullScreen + hostViewController.present(navigationViewController, animated: true, completion: nil) + } } } diff --git a/ownCloud/Bookmarks/Composer/BookmarkComposer.swift b/ownCloud/Bookmarks/Composer/BookmarkComposer.swift new file mode 100644 index 000000000..2b158de53 --- /dev/null +++ b/ownCloud/Bookmarks/Composer/BookmarkComposer.swift @@ -0,0 +1,533 @@ +// +// BookmarkComposer.swift +// ownCloud +// +// Created by Felix Schwarz on 05.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudAppShared + +class BookmarkComposer: NSObject { + // MARK: - Steps + enum Step: Equatable, Hashable { + case intro + case enterUsername + case serverURL(urlString: String?) + case authenticate(withCredentials: Bool, username: String?, password: String?) + case chooseServer(fromInstances: [OCServerInstance]) + case infinitePropfind + case completed + } + + struct UndoAction { + var action: (_ composer: BookmarkComposer) -> Void + var byUser: Bool + } + + var configuration: BookmarkComposerConfiguration + weak var delegate: BookmarkComposerDelegate? + + init(configuration: BookmarkComposerConfiguration, removeAuthDataFromCopy: Bool = false, delegate: BookmarkComposerDelegate?) { + self.configuration = configuration + self.delegate = delegate + + self.bookmark = configuration.bookmark?.copy() as? OCBookmark ?? OCBookmark() + + bookmark.authenticationDataStorage = .memory // Disconnect bookmark from keychain + + if bookmark.isTokenBased == true, removeAuthDataFromCopy { + bookmark.authenticationData = nil + } + + if bookmark.scanForAuthenticationMethodsRequired == true { + bookmark.authenticationMethodIdentifier = nil + bookmark.authenticationData = nil + } + + if let name = configuration.name { + bookmark.name = name + } + } + + // MARK: - Internal storage + var bookmark: OCBookmark + + // MARK: - Connection instantiation + private var _cookieStorage : OCHTTPCookieStorage? + var cookieStorage : OCHTTPCookieStorage? { + if _cookieStorage == nil, let cookieSupportEnabled = OCCore.classSetting(forOCClassSettingsKey: .coreCookieSupportEnabled) as? Bool, cookieSupportEnabled == true { + _cookieStorage = OCHTTPCookieStorage() + Log.debug("Created cookie storage \(String(describing: _cookieStorage))") + } + + return _cookieStorage + } + + func instantiateConnection(for bmark: OCBookmark) -> OCConnection { + let connection = OCConnection(bookmark: bmark) + + connection.hostSimulator = OCHostSimulatorManager.shared.hostSimulator(forLocation: .accountSetup, for: self) + connection.cookieStorage = self.cookieStorage // Share cookie storage across all relevant connections + + return connection + } + + // MARK: - Setup steps + private var didShowIntro: Bool = false + private var username: String? + private var password: String? + private var instances: [OCServerInstance]? + private var supportsInfinitePropfind: Bool? + private var performedPrepopulation: Bool = false + private var isDriveBased: Bool? + private var generationOptions: [OCAuthenticationMethodKey : Any]? + + // MARK: .intro + func doneIntro() { + didShowIntro = true + + self.pushUndoAction(undoAction: UndoAction(action: { composer in + composer.didShowIntro = false + }, byUser: true)) + + self.updateState() + } + + // MARK: .enterUsername + func enterUsername(_ username: String, byUser: Bool = true, completion: @escaping Completion) { + bookmark.serverLocationUserName = username + self.username = username + + self.pushUndoAction(undoAction: UndoAction(action: { composer in + composer.bookmark.serverLocationUserName = nil + composer.username = nil + }, byUser: byUser)) + + completion(nil, nil, nil) + self.updateState() + } + + // MARK: .enterURL + typealias Completion = (_ error: Error?, _ issue: OCIssue?, _ issueCompletionHandler: IssuesCardViewController.CompletionHandler?) -> Void + + func enterURL(_ urlString: String, byUser: Bool = true, completion: @escaping Completion) { + var username : NSString?, password: NSString? + var protocolWasPrepended : ObjCBool = false + + // Normalize URL + guard let serverURL = NSURL(username: &username, password: &password, afterNormalizingURLString: urlString, protocolWasPrepended: &protocolWasPrepended) as URL? else { + return + } + + // Check for zero-length host name + if (serverURL.host == nil) || ((serverURL.host != nil) && (serverURL.host?.count==0)) { + // Missing hostname + completion(nil, OCIssue(localizedTitle: "Missing hostname".localized, localizedDescription: "The entered URL does not include a hostname.".localized, level: .error), nil) + return + } + + // Save username and password for possible later use if they were part of the URL + if username != nil { + self.username = username as? String + } + + if password != nil { + self.password = password as? String + } + + // Probe URL + bookmark.url = serverURL + + let connection = instantiateConnection(for: bookmark) + + hudMessage = "Contacting server…".localized + + connection.prepareForSetup(options: nil) { [weak self] (issue, _, _, preferredAuthenticationMethods, generationOptions) in + self?.hudMessage = nil + + let continueToNextStep : (_ allowRetry: Bool) -> Void = { [weak self] (allowRetry) in + if let preferredAuthenticationMethods, preferredAuthenticationMethods.count > 0 { + self?.bookmark.authenticationMethodIdentifier = preferredAuthenticationMethods.first + self?.pushUndoAction(undoAction: UndoAction(action: { composer in + composer.username = nil + composer.password = nil + + composer.bookmark.url = nil + composer.bookmark.authenticationMethodIdentifier = nil + composer.bookmark.certificateStore?.removeAllCertificates() + + composer.generationOptions = nil + }, byUser: byUser)) + self?.updateState() + } else if allowRetry { + self?.enterURL(urlString, completion: completion) + } + } + + self?.generationOptions = generationOptions + + if let issue { + // Parse issue for display + if issue.prepareForDisplay().isAtLeast(level: .warning) { + // Present issues if the level is >= warning + completion(nil, issue, { [weak self, weak issue] (response) in + switch response { + case .cancel: + issue?.reject() + self?.bookmark.url = nil + + case .approve: + issue?.approve() + continueToNextStep(true) + + case .dismiss: + self?.bookmark.url = nil + } + }) + } else { + // Do not present issues + issue.approve() + continueToNextStep(true) + } + } else { + continueToNextStep(false) + } + } + } + + // MARK: .authenticate + func authenticate(username: String? = nil, password: String? = nil, presentingViewController: UIViewController?, completion: @escaping Completion) { + var options : [OCAuthenticationMethodKey : Any] = generationOptions ?? [:] + + let connection = instantiateConnection(for: bookmark) + + if let authMethodIdentifier = bookmark.authenticationMethodIdentifier { + if OCAuthenticationMethod.isAuthenticationMethodPassphraseBased(authMethodIdentifier as OCAuthenticationMethodIdentifier) { + options[.usernameKey] = username ?? "" + options[.passphraseKey] = password ?? "" + } + } + + options[.presentingViewControllerKey] = presentingViewController + options[.requiredUsernameKey] = bookmark.userName + + // Pre-fill already provided username in case of a server locator being used + if options[.requiredUsernameKey] == nil, let serverLocationUserName = bookmark.serverLocationUserName { + options[.usernameKey] = serverLocationUserName + } + + guard let bookmarkAuthenticationMethodIdentifier = bookmark.authenticationMethodIdentifier else { return } + + hudMessage = "Authenticating…".localized + + connection.generateAuthenticationData(withMethod: bookmarkAuthenticationMethodIdentifier, options: options) { (error, authMethodIdentifier, authMethodData) in + if error == nil, let authMethodIdentifier, let authMethodData { + self.bookmark.authenticationMethodIdentifier = authMethodIdentifier + self.bookmark.authenticationData = authMethodData + self.bookmark.scanForAuthenticationMethodsRequired = false + + self.hudMessage = "Fetching user information…".localized + + // Retrieve available instances for this account to chose from + connection.retrieveAvailableInstances(options: options, authenticationMethodIdentifier: authMethodIdentifier, authenticationData: authMethodData, completionHandler: { error, instances in + if error == nil, let instances, instances.count > 0 { + self.instances = instances + } + + if self.bookmark.isComplete { + self.bookmark.authenticationDataStorage = .keychain // Commit auth changes to keychain + } + + let continueCompletion : Completion = { (error, issue, issueCompletionHandler) in + self.hudMessage = nil + + completion(error,issue,issueCompletionHandler) + + if error == nil, issue == nil { + self.clearUndoStack() + self.updateState() + } + } + + if self.instances == nil { + // bookmark URL final -> retrieve server configuration right away + self.retrieveServerConfiguration(completion: continueCompletion) + } else { + // server instance needs to be chosen + if self.instances?.count == 1, let onlyInstance = self.instances?.first { + // If only one instance is returned, choose it right away + self.chooseServer(instance: onlyInstance, byUser: false, completion: continueCompletion) + } else { + continueCompletion(nil, nil, nil) + } + } + + Log.debug("\(connection) returned error=\(String(describing: error)) instances=\(String(describing: instances))") // Debug message also has the task to capture connection and avoid it being prematurely dropped + }) + } else { + self.hudMessage = nil + + var issue : OCIssue? + let nsError = error as NSError? + + if let embeddedIssue = nsError?.embeddedIssue() { + issue = embeddedIssue + } else if let error = error { + issue = OCIssue(forError: error, level: .error, issueHandler: nil) + } + + if nsError?.isOCError(withCode: .authorizationFailed) == true { + // Shake + completion(error, nil, nil) + } else if nsError?.isOCError(withCode: .authorizationCancelled) == true { + // User cancelled authorization, no reaction needed + } else if let issue { + completion(nil, issue, { [weak self, weak issue] (response) in + switch response { + case .cancel: + issue?.reject() + + case .approve: + issue?.approve() + self?.updateState() + + case .dismiss: break + } + }) + } + } + } + } + + func retrieveServerConfiguration(completion: @escaping Completion) { + let connection = instantiateConnection(for: bookmark) + + self.hudMessage = "Fetching server information…".localized + + connection.connect { [weak self] (error, issue) in + guard let strongSelf = self else { return } + + // Handle errors + guard error == nil, issue == nil else { + self?.hudMessage = nil + + completion(error, issue, { [weak self, weak issue] (response) in + switch response { + case .cancel: + issue?.reject() + + case .approve: + issue?.approve() + self?.updateState() + + case .dismiss: break + } + }) + return + } + + // Inspect server configuration + strongSelf.bookmark.userDisplayName = connection.loggedInUser?.displayName + + strongSelf.isDriveBased = connection.capabilities?.spacesEnabled?.boolValue ?? false + strongSelf.supportsInfinitePropfind = connection.capabilities?.davPropfindSupportsDepthInfinity?.boolValue ?? false + + connection.disconnect(completionHandler: { + self?.hudMessage = nil + completion(nil, nil, nil) + }) + } + } + + // MARK: .chooseServer + func chooseServer(instance: OCServerInstance, byUser: Bool = true, completion: @escaping Completion) { + // Apply instance + self.bookmark.apply(instance) + + // Drop all other choices + self.instances = nil + + // Retrieve server configuration after instance changes have been applied + self.retrieveServerConfiguration(completion: completion) + } + + // MARK: .prepopulate + func prepopulate(completion: @escaping Completion) -> Progress? { + var prepopulationMethod : BookmarkPrepopulationMethod? + + // Determine prepopulation method + if prepopulationMethod == nil, let prepopulationMethodClassSetting = BookmarkViewController.classSetting(forOCClassSettingsKey: .prepopulation) as? String { + prepopulationMethod = BookmarkPrepopulationMethod(rawValue: prepopulationMethodClassSetting) + } + + if prepopulationMethod == nil, supportsInfinitePropfind == true { + prepopulationMethod = .streaming + } + + if prepopulationMethod == nil { + prepopulationMethod = .doNot + } + + if isDriveBased == true { + // Drive-based accounts do not support prepopulation yet + prepopulationMethod = .doNot + } + + performedPrepopulation = true + + // Prepopulation y/n? + if let prepopulationMethod, prepopulationMethod != .doNot { + // Perform prepopulation + var prepopulateProgress : Progress? + + // Perform prepopulation method + switch prepopulationMethod { + case .streaming: + prepopulateProgress = bookmark.prepopulate(streamCompletionHandler: { [weak self] _ in + completion(nil, nil, nil) + self?.updateState() + }) + + case .split: + prepopulateProgress = bookmark.prepopulate(completionHandler: { [weak self] _ in + completion(nil, nil, nil) + self?.updateState() + }) + + default: + completion(nil, nil, nil) + self.updateState() + } + + // Present progress + return prepopulateProgress + } + + // No prepopulation + completion(nil, nil, nil) + self.updateState() + + return nil + } + + // MARK: .finished + func setName(_ bookmarkName: String?) { + self.bookmark.name = bookmarkName + } + + // MARK: - Undo + var undoStack: [UndoAction] = [] + + var canUndoLastStep: Bool { + return !undoStack.isEmpty + } + + func clearUndoStack() { + undoStack.removeAll() + } + + func pushUndoAction(undoAction: UndoAction) { + undoStack.append(undoAction) + } + + func undoLastStep() { + if undoStack.count > 0 { + while undoStack.last != nil { + let undoAction = undoStack.removeLast() + undoAction.action(self) + + if undoAction.byUser { + break + } + } + + updateState() + } + } + + // MARK: - State + var currentStep: Step? { + didSet { + if oldValue != currentStep, let currentStep { + delegate?.present(composer: self, step: currentStep) + } + } + } + var hudMessage: String? { + didSet { + if hudMessage != oldValue { + delegate?.present(composer: self, hudMessage: hudMessage) + } + } + } + + func updateState() { + if configuration.hasIntro, !didShowIntro { + currentStep = .intro + } else if OCServerLocator.useServerLocatorIdentifier != nil, bookmark.serverLocationUserName == nil { + currentStep = .enterUsername + } else if bookmark.url == nil { + if let absoluteURL = configuration.url?.absoluteString, !configuration.urlEditable { + enterURL(absoluteURL, byUser: false, completion: { [weak self] error, issue, issueCompletionHandler in + if let self { + self.delegate?.present(composer: self, error: error, issue: issue, issueCompletionHandler: issueCompletionHandler) + } + }) + } else { + currentStep = .serverURL(urlString: configuration.url?.absoluteString) + } + } else if bookmark.authenticationData == nil { + currentStep = .authenticate(withCredentials: bookmark.isTokenBased == false, username: username, password: password) + } else if let instances, instances.count > 0 { + currentStep = .chooseServer(fromInstances: instances) + } else if supportsInfinitePropfind == true, !performedPrepopulation { + currentStep = .infinitePropfind + } else { + currentStep = .completed + } + } + + // MARK: - Add or update bookmark + func addBookmark() -> OCBookmark { + OCBookmarkManager.shared.addBookmark(bookmark) + + return bookmark + } + + func updateBookmark(_ originalBookmark: OCBookmark? = nil) { + guard let originalBookmark = originalBookmark ?? configuration.bookmark else { + return + } + + originalBookmark.setValuesFrom(bookmark) + + if !OCBookmarkManager.shared.updateBookmark(originalBookmark) { + Log.error("Changes to \(originalBookmark) not saved as it's not tracked by OCBookmarkManager!") + } + } +} + +protocol BookmarkComposerDelegate : AnyObject { + func present(composer: BookmarkComposer, step: BookmarkComposer.Step) + func present(composer: BookmarkComposer, error: Error?, issue: OCIssue?, issueCompletionHandler: IssuesCardViewController.CompletionHandler?) + func present(composer: BookmarkComposer, hudMessage: String?) +} + +extension OCBookmark { + var isComplete: Bool { + return url != nil && authenticationMethodIdentifier != nil && authenticationData != nil + } +} diff --git a/ownCloud/Bookmarks/Composer/BookmarkComposerConfiguration.swift b/ownCloud/Bookmarks/Composer/BookmarkComposerConfiguration.swift new file mode 100644 index 000000000..f43c8971c --- /dev/null +++ b/ownCloud/Bookmarks/Composer/BookmarkComposerConfiguration.swift @@ -0,0 +1,58 @@ +// +// BookmarkComposerConfiguration.swift +// ownCloud +// +// Created by Felix Schwarz on 05.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp +import ownCloudAppShared + +class BookmarkComposerConfiguration { + var bookmark: OCBookmark? + + var hasIntro: Bool = false + var hasSettings: Bool = false + + var url: URL? + var urlEditable: Bool = true + + var name: String? + var nameEditable: Bool = true + + var helpButtonLabel: String? + var helpButtonURL: URL? + var helpMessage: String? + + init(bookmark: OCBookmark? = nil, hasIntro: Bool = false, hasSettings: Bool = true, url: URL? = nil, urlEditable: Bool = true, name: String? = nil, nameEditable: Bool, helpButtonLabel: String? = nil, helpButtonURL: URL? = nil, helpMessage: String? = nil) { + self.bookmark = bookmark + self.hasIntro = hasIntro + self.hasSettings = hasSettings + self.url = url + self.urlEditable = urlEditable + self.name = name + self.nameEditable = nameEditable + self.helpButtonLabel = helpButtonLabel + self.helpButtonURL = helpButtonURL + self.helpMessage = helpMessage + } +} + +extension BookmarkComposerConfiguration { + static var newBookmarkConfiguration: BookmarkComposerConfiguration { + return BookmarkComposerConfiguration(url: Branding.shared.profileURL, urlEditable: Branding.shared.profileAllowUrlConfiguration ?? true, name: Branding.shared.profileBookmarkName, nameEditable: Branding.shared.canEditAccount, helpButtonLabel: Branding.shared.profileHelpButtonLabel, helpButtonURL: Branding.shared.profileHelpURL, helpMessage: Branding.shared.profileOpenHelpMessage) + } +} diff --git a/ownCloud/Bookmarks/Setup/BookmarkSetupStepViewController.swift b/ownCloud/Bookmarks/Setup/BookmarkSetupStepViewController.swift new file mode 100644 index 000000000..fe35d5ac6 --- /dev/null +++ b/ownCloud/Bookmarks/Setup/BookmarkSetupStepViewController.swift @@ -0,0 +1,293 @@ +// +// BookmarkSetupStepViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 06.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudAppShared + +extension BookmarkComposer.Step { + var cssSelector: ThemeCSSSelector { + switch self { + case .intro: return ThemeCSSSelector(rawValue: "intro") + case .enterUsername: return ThemeCSSSelector(rawValue: "enterUsername") + case .serverURL(urlString: _): return ThemeCSSSelector(rawValue: "serverURL") + case .authenticate(withCredentials: _, username: _, password: _): return ThemeCSSSelector(rawValue: "authenticate") + case .chooseServer(fromInstances: _): return ThemeCSSSelector(rawValue: "chooseServer") + case .infinitePropfind: return ThemeCSSSelector(rawValue: "infinitePropfind") + case .completed: return ThemeCSSSelector(rawValue: "completed") + } + } +} + +class BookmarkSetupStepViewController: UIViewController, UITextFieldDelegate { + weak var setupViewController: BookmarkSetupViewController? + var backgroundView: ThemeCSSView + var step: BookmarkComposer.Step + + var cacheViewController: Bool = true + + init(with setupViewController: BookmarkSetupViewController, step: BookmarkComposer.Step) { + self.setupViewController = setupViewController + self.step = step + self.backgroundView = ThemeCSSView(withSelectors: [.background]) + self.backgroundView.translatesAutoresizingMaskIntoConstraints = false + + composerCompletion = { [weak setupViewController] (error, issue, issueCompletionHandler) in + if let setupViewController, let composer = setupViewController.composer { + setupViewController.present(composer: composer, error: error, issue: issue, issueCompletionHandler: issueCompletionHandler) + } + } + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var stepTitle: String? + var stepMessage: String? + var continueButtonLabelText: String? = "Proceed".localized { + didSet { + var buttonConfiguration = continueButton.configuration + buttonConfiguration?.title = continueButtonLabelText + continueButton.configuration = buttonConfiguration + } + } + var backButtonLabelText: String? = "Back".localized { + didSet { + var buttonConfiguration = backButton.configuration + buttonConfiguration?.title = backButtonLabelText + backButton.configuration = buttonConfiguration + } + } + + var titleLabel: UILabel? + var messageLabel: UILabel? + var continueButton: UIButton = ThemeButton(withSelectors: [.filled]) + var backButton: UIButton = ThemeCSSButton(withSelectors: []) + + var contentContainerView: UIView? + var contentView: UIView? { + willSet { + if newValue != contentView { + contentView?.removeFromSuperview() + } + } + + didSet { + if let contentView { + contentContainerView?.embed(toFillWith: contentView) + } + } + } + + var bookmark: OCBookmark? { + return setupViewController?.composer?.bookmark + } + + var composerCompletion: BookmarkComposer.Completion + + var topViews: [UIView]? + var topViewsSpacing: CGFloat = 10 + var bottomViews: [UIView]? + var bottomViewsSpacing: CGFloat = 10 + + override func loadView() { + var views: [UIView] = topViews ?? [] + + let contentView = UIView() + contentView.cssSelectors = [.step, step.cssSelector] + + // Title & message + if let stepTitle = textOverride(for: "title") ?? stepTitle { + titleLabel = ThemeCSSLabel(withSelectors: [.title]) + titleLabel?.translatesAutoresizingMaskIntoConstraints = false + titleLabel?.text = stepTitle + titleLabel?.font = UIFont.preferredFont(forTextStyle: .headline, with: .bold) + titleLabel?.makeLabelWrapText() + + views.append(titleLabel!) + } + + if let stepMessage = textOverride(for: "message") ?? stepMessage { + messageLabel = ThemeCSSLabel(withSelectors: [.message]) + messageLabel?.translatesAutoresizingMaskIntoConstraints = false + messageLabel?.text = stepMessage + messageLabel?.font = UIFont.preferredFont(forTextStyle: .subheadline, with: .regular) + messageLabel?.makeLabelWrapText() + + views.append(messageLabel!) + } + + // Content container view + contentContainerView = UIView() + contentContainerView?.translatesAutoresizingMaskIntoConstraints = false + + if let contentContainerView { + views.append(contentContainerView) + } + + // Continue Button + var buttonConfiguration = UIButton.Configuration.borderedProminent() + buttonConfiguration.title = continueButtonLabelText + buttonConfiguration.cornerStyle = .large + + continueButton.translatesAutoresizingMaskIntoConstraints = false + continueButton.configuration = buttonConfiguration + continueButton.addAction(UIAction(handler: { [weak self] _ in + self?.handleContinue() + }), for: .primaryActionTriggered) + + views.append(continueButton) + + // Back button + if hasBackButton { + buttonConfiguration = UIButton.Configuration.plain() + buttonConfiguration.title = backButtonLabelText + buttonConfiguration.cornerStyle = .large + + backButton.translatesAutoresizingMaskIntoConstraints = false + backButton.configuration = buttonConfiguration + backButton.addAction(UIAction(handler: { [weak self] _ in + self?.handleBack() + }), for: .primaryActionTriggered) + + views.append(backButton) + } + + // Bottom views + if let bottomViews { + views.append(contentsOf: bottomViews) + } + + // Embed background view + self.backgroundView.layer.cornerRadius = 10 + self.backgroundView.layer.masksToBounds = true + contentView.embed(toFillWith: backgroundView) + + // Layout views vertically in background view + contentView.embedVertically(views: views, insets: NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20), spacingProvider: { leadingView, trailingView in + if leadingView == self.topViews?.last { + return self.topViewsSpacing + } + if trailingView == self.bottomViews?.first { + return self.bottomViewsSpacing + } + if trailingView == self.contentContainerView { + return 15 + } + if leadingView == self.contentContainerView { + return 30 + } + return 5 + }, centered: false) + + // Set view controller's view + self.view = contentView + } + + func handleContinue() { + } + + var hasBackButton: Bool { + return canGoBack + } + + var canGoBack: Bool { + return setupViewController?.canGoBack ?? false + } + + func handleBack() { + self.setupViewController?.goBack() + } + + func present(error: Error?, issue: OCIssue?, issueCompletionHandler: IssuesCardViewController.CompletionHandler?) { + if let composer = setupViewController?.composer, error != nil || issue != nil { + self.setupViewController?.present(composer: composer, error: error, issue: issue, issueCompletionHandler: issueCompletionHandler) + } + } + + func buildTextField(withAction textChangedAction: UIAction?, forEvent actionEvent: UIControl.Event = .editingChanged, placeholder placeholderString: String = "", value textValue: String = "", secureTextEntry : Bool = false, keyboardType: UIKeyboardType = .default, autocorrectionType: UITextAutocorrectionType = .default, autocapitalizationType: UITextAutocapitalizationType = UITextAutocapitalizationType.none, enablesReturnKeyAutomatically: Bool = true, returnKeyType : UIReturnKeyType = .default, inputAccessoryView : UIView? = nil, identifier : String? = nil, accessibilityLabel: String? = nil, clearButtonMode : UITextField.ViewMode = .never, borderStyle: UITextField.BorderStyle = .none) -> UITextField { + let textField : UITextField = ThemeCSSTextField() + + textField.translatesAutoresizingMaskIntoConstraints = false + + textField.placeholder = placeholderString + textField.keyboardType = keyboardType + textField.autocorrectionType = autocorrectionType + textField.isSecureTextEntry = secureTextEntry + textField.autocapitalizationType = autocapitalizationType + textField.enablesReturnKeyAutomatically = enablesReturnKeyAutomatically + textField.returnKeyType = returnKeyType + textField.inputAccessoryView = inputAccessoryView + textField.text = textValue + textField.accessibilityIdentifier = identifier + textField.clearButtonMode = clearButtonMode + textField.borderStyle = borderStyle + + if let textChangedAction { + textField.addAction(textChangedAction, for: actionEvent) + } + + textField.accessibilityLabel = accessibilityLabel + + textField.setContentHuggingPriority(.required, for: .vertical) + textField.setContentCompressionResistancePriority(.required, for: .horizontal) + + return textField + } + + var focusTextFields: [UITextField]? { + didSet { + if let focusTextFields { + for textField in focusTextFields { + textField.delegate = self + } + } + } + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if let focusTextFields, let position = focusTextFields.firstIndex(of: textField) { + if position < (focusTextFields.count-1) { + let nextTextField = focusTextFields[position+1] + nextTextField.becomeFirstResponder() + + return false + } + } + + if continueButton.isEnabled { + handleContinue() + } + + return false + } + + func textOverride(for label: String) -> String? { + let key = "setup-\(step.cssSelector.rawValue)-\(label)" + let localization = key.localized + + if localization != key { + return localization + } + + return nil + } +} diff --git a/ownCloud/Bookmarks/Setup/BookmarkSetupViewController.swift b/ownCloud/Bookmarks/Setup/BookmarkSetupViewController.swift new file mode 100644 index 000000000..750ac0a7e --- /dev/null +++ b/ownCloud/Bookmarks/Setup/BookmarkSetupViewController.swift @@ -0,0 +1,359 @@ +// +// BookmarkSetupViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 05.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp +import ownCloudAppShared + +class BookmarkSetupViewController: EmbeddingViewController, BookmarkComposerDelegate { + var composer: BookmarkComposer? + var configuration: BookmarkComposerConfiguration + + var stepControllerByStep : [BookmarkComposer.Step : UIViewController] = [:] + + var visibleContentContainerView: UIView = UIView() + + var helpMessageView: ComposedMessageView? + + private var centerHelperView: UIView = UIView() + private var logoView: UIView? + + init(configuration: BookmarkComposerConfiguration, cancelHandler: CancelHandler? = nil, doneHandler: DoneHandler? = nil) { + self.configuration = configuration + + super.init(nibName: nil, bundle: nil) + + self.doneHandler = doneHandler + self.cancelHandler = cancelHandler + + composer = BookmarkComposer(configuration: configuration, delegate: self) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + let contentView = UIView() + + visibleContentContainerView.translatesAutoresizingMaskIntoConstraints = false + + let brandBackground = BrandView(showBackground: true, showLogo: false, roundedCorners: false, assetSuffix: .setup) + + contentView.embed(toFillWith: brandBackground) + contentView.embed(toFillWith: visibleContentContainerView, enclosingAnchors: contentView.safeAreaWithKeyboardAnchorSet) + + centerHelperView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(centerHelperView) + + self.cssSelectors = [.modal, .accountSetup] + + // Add logo + if navigationController == nil { + let deviceScreenHeight = UIScreen.main.bounds.height + let logoMaxHeight = deviceScreenHeight < 800 ? (deviceScreenHeight < 600 ? 48 : 96) : 128 + let maxLogoSize = CGSize(width: 256, height: logoMaxHeight) + logoView = BrandView(showBackground: false, showLogo: true, logoMaxSize: maxLogoSize, fitToLogo: true, roundedCorners: false, assetSuffix: .setup) + } + + if let logoView { + contentView.addSubview(logoView) + + NSLayoutConstraint.activate([ + centerHelperView.leadingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.leadingAnchor).with(priority: .defaultLow), + centerHelperView.trailingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.trailingAnchor).with(priority: .defaultLow), + + centerHelperView.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor).with(priority: .defaultLow), + centerHelperView.heightAnchor.constraint(greaterThanOrEqualToConstant: 1), + + logoView.topAnchor.constraint(equalTo: centerHelperView.topAnchor), + logoView.centerXAnchor.constraint(equalTo: centerHelperView.centerXAnchor) + ]) + } + + // Add cancel button + if cancelHandler != nil { + if navigationController != nil { + navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction(handler: { [weak self] action in + self?.cancel() + })) + } else { + let cancelButton = ThemeCSSButton(withSelectors: [.cancel]) + cancelButton.translatesAutoresizingMaskIntoConstraints = false + cancelButton.setTitle("Cancel".localized, for: .normal) + cancelButton.addAction(UIAction(handler: { [weak self] _ in + self?.cancel() + }), for: .primaryActionTriggered) + + contentView.addSubview(cancelButton) + + NSLayoutConstraint.activate([ + cancelButton.trailingAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.trailingAnchor, constant: -20), + cancelButton.topAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.topAnchor, constant: 20) + ]) + } + } + + // Add help message + if configuration.helpButtonURL != nil || configuration.helpMessage != nil { + var helpElements: [ComposedMessageElement] = [] + + if let helpButtonURL = configuration.helpButtonURL { + helpElements += [ + .button(configuration.helpButtonLabel ?? "Open help page".localized, action: UIAction(handler: { action in + UIApplication.shared.open(helpButtonURL) + }), image: UIImage(systemName: "questionmark.circle")) + ] + } + + if let helpMessage = configuration.helpMessage { + helpElements += [ + .subtitle(helpMessage, alignment: .centered) + ] + } + + let helpMessageView = ComposedMessageView(elements: helpElements) + helpMessageView.cssSelectors = [ .help ] + helpMessageView.elementInsets = NSDirectionalEdgeInsets(top: 0, leading: 15, bottom: 10, trailing: 15) + helpMessageView.setContentHuggingPriority(.required, for: .vertical) + + contentView.addSubview(helpMessageView) + + NSLayoutConstraint.activate([ + helpMessageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + helpMessageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + helpMessageView.bottomAnchor.constraint(equalTo: contentView.keyboardLayoutGuide.topAnchor) + ]) + } + + view = contentView + } + + override func viewDidLoad() { + super.viewDidLoad() + + composer?.updateState() + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return [.portrait, .portraitUpsideDown] + } + + override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + return .portrait + } + + override var preferredStatusBarStyle : UIStatusBarStyle { + return Theme.shared.activeCollection.css.getStatusBarStyle(for: self) ?? .default + } + + // MARK: - Steps view controller and layout + func viewController(for step: BookmarkComposer.Step) -> UIViewController? { + if let stepViewController = stepControllerByStep[step] { + return stepViewController + } + + var stepViewController: UIViewController? + + switch step { + case .intro: + stepViewController = BookmarkSetupStepIntroViewController(with: self, step: step) + + case .serverURL(urlString: _): + stepViewController = BookmarkSetupStepEnterURLViewController(with: self, step: step) + + case .enterUsername: + stepViewController = BookmarkSetupStepEnterUsernameViewController(with: self, step: step) + + case .authenticate(withCredentials: _, username: _, password: _): + stepViewController = BookmarkSetupStepAuthenticateViewController(with: self, step: step) + + case .chooseServer(fromInstances: _): + // Do not support server choice for now (also see present(composer:step:) + break + + case .infinitePropfind: + stepViewController = BookmarkSetupStepPrepopulateViewController(with: self, step: step) + + case .completed: + if composer?.configuration.nameEditable == true { + stepViewController = BookmarkSetupStepFinishedViewController(with: self, step: step) + } else { + let bookmark = composer?.addBookmark() + done(bookmark: bookmark) + } + } + + (stepViewController as? BookmarkSetupStepViewController)?.setupViewController = self + + if (stepViewController as? BookmarkSetupStepViewController)?.cacheViewController == true { + stepControllerByStep[step] = stepViewController + } + + return stepViewController + } + + override func constraintsForEmbedding(contentViewController: UIViewController) -> [NSLayoutConstraint] { + let contentViewControllerView = contentViewController.view! + var constraints: [NSLayoutConstraint] + + if let logoView { + constraints = visibleContentContainerView.embed(centered: centerHelperView, minimumInsets: NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20), constraintsOnly: true) + + constraints += [ + contentViewControllerView.topAnchor.constraint(equalTo: logoView.safeAreaLayoutGuide.bottomAnchor, constant: 10), + contentViewControllerView.bottomAnchor.constraint(equalTo: centerHelperView.safeAreaLayoutGuide.bottomAnchor), + contentViewControllerView.leadingAnchor.constraint(equalTo: centerHelperView.safeAreaLayoutGuide.leadingAnchor), + contentViewControllerView.trailingAnchor.constraint(equalTo: centerHelperView.safeAreaLayoutGuide.trailingAnchor) + ] + } else { + constraints = visibleContentContainerView.embed(centered: contentViewControllerView, minimumInsets: NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20), constraintsOnly: true) + } + + return constraints + } + + // MARK: - HUD message + var hudMessageView: UIView? { + willSet { + hudMessageView?.removeFromSuperview() + + if newValue == nil { + contentViewController?.view.isHidden = false + logoView?.isHidden = false + } + } + + didSet { + if let hudMessageView { + visibleContentContainerView.addSubview(hudMessageView) + NSLayoutConstraint.activate([ + hudMessageView.centerYAnchor.constraint(equalTo: visibleContentContainerView.centerYAnchor), + hudMessageView.centerXAnchor.constraint(equalTo: visibleContentContainerView.safeAreaLayoutGuide.centerXAnchor), + hudMessageView.leadingAnchor.constraint(greaterThanOrEqualTo: visibleContentContainerView.safeAreaLayoutGuide.leadingAnchor, constant: 20), + hudMessageView.trailingAnchor.constraint(lessThanOrEqualTo: visibleContentContainerView.safeAreaLayoutGuide.trailingAnchor, constant: -20) + ]) + contentViewController?.view.isHidden = true + logoView?.isHidden = true + } + } + } + + // MARK: - History support + var canGoBack: Bool { + return composer?.canUndoLastStep ?? false + } + + func goBack() { + if canGoBack { + composer?.undoLastStep() + } + } + + // MARK: - Dismiss + typealias CancelHandler = () -> Void + var cancelHandler: CancelHandler? + + func cancel() { + cancelHandler?() + cancelHandler = nil + } + + typealias DoneHandler = (_ bookmark: OCBookmark?) -> Void + var doneHandler: DoneHandler? + + func done(bookmark: OCBookmark?) { + doneHandler?(bookmark) + doneHandler = nil + } + + // MARK: - Bookmark Composer Delegate + func present(composer: BookmarkComposer, step: BookmarkComposer.Step) { + if case let .chooseServer(fromInstances: instances) = step { + // Do not support server choice for now (also see viewController(for:) + // Pick first server from list + composer.chooseServer(instance: instances.first!, completion: { [weak self] error, issue, issueCompletionHandler in + guard let self, let composer = self.composer else { return } + self.present(composer: composer, error: error, issue: issue, issueCompletionHandler: issueCompletionHandler) + }) + } else { + OnMainThread { + let stepViewController = self.viewController(for: step) + stepViewController?.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 400).with(priority: .defaultHigh).isActive = true + self.contentViewController = stepViewController + } + } + } + + func present(composer: BookmarkComposer, hudMessage: String?) { + OnMainThread { + if let hudMessage { + let indeterminateProgress = Progress.indeterminate() + indeterminateProgress.isCancellable = false + + let messageView = ComposedMessageView.infoBox(additionalElements: [ + .progressCircle(with: indeterminateProgress, alignment: .centered), + .spacing(10), + .text(hudMessage, style: .system(textStyle: .headline, weight: .bold), alignment: .centered) + ]) + messageView.cssSelectors = [ .step ] + + messageView.widthAnchor.constraint(greaterThanOrEqualToConstant: 400).with(priority: .defaultHigh).isActive = true + + self.hudMessageView = messageView + } else { + self.hudMessageView = nil + } + } + } + + func present(composer: BookmarkComposer, error: Error?, issue: OCIssue?, issueCompletionHandler: IssuesCardViewController.CompletionHandler?) { + OnMainThread { + var presentIssue: OCIssue? = issue + var completionHandler = issueCompletionHandler + + if presentIssue == nil, let error { + presentIssue = OCIssue(forError: error, level: .warning) + } + + if completionHandler == nil { + completionHandler = { [weak presentIssue] (response) in + switch response { + case .cancel: + presentIssue?.reject() + + case .approve: + presentIssue?.approve() + + case .dismiss: break + } + } + } + + if let presentIssue, let completionHandler { + let displayIssues = presentIssue.prepareForDisplay() + + IssuesCardViewController.present(on: self, issue: presentIssue, displayIssues: displayIssues, completion: completionHandler) + } + } + } +} + +extension ThemeCSSSelector { +} diff --git a/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepAuthenticateViewController.swift b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepAuthenticateViewController.swift new file mode 100644 index 000000000..dd5087c56 --- /dev/null +++ b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepAuthenticateViewController.swift @@ -0,0 +1,114 @@ +// +// BookmarkSetupStepAuthenticateViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 06.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +class BookmarkSetupStepAuthenticateViewController: BookmarkSetupStepViewController { + var usernameField: UITextField? + var passwordField: UITextField? + + override init(with setupViewController: BookmarkSetupViewController, step: BookmarkComposer.Step) { + super.init(with: setupViewController, step: step) + cacheViewController = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + guard case let .authenticate(withCredentials: withCredentials, username: prefillUsername, password: prefillPassword) = step else { + return + } + + if withCredentials { + continueButtonLabelText = "Login".localized + } else { + stepTitle = "Login".localized + stepMessage = "If you 'Continue', you will be prompted to allow the '{{app.name}}' app to open the login page where you can enter your credentials.".localized + continueButtonLabelText = "Open login page".localized + } + + let certificateSummaryView = CertificateSummaryView(with: bookmark?.primaryCertificate, httpHostname: bookmark?.url?.host) + certificateSummaryView.translatesAutoresizingMaskIntoConstraints = false + + if withCredentials { + self.topViews = [ certificateSummaryView ] + self.topViewsSpacing = 20 + } + + super.loadView() + + if withCredentials { + usernameField = buildTextField(withAction: UIAction(handler: { [weak self] _ in + self?.updateState() + }), placeholder: "Username", value: prefillUsername ?? "", autocorrectionType: .no, autocapitalizationType: .none, accessibilityLabel: "Server Username".localized, borderStyle: .roundedRect) + usernameField?.textContentType = .username + + passwordField = buildTextField(withAction: UIAction(handler: { [weak self] _ in + self?.updateState() + }), placeholder: "Password", value: prefillPassword ?? "", secureTextEntry: true, autocorrectionType: .no, autocapitalizationType: .none, accessibilityLabel: "Server Password".localized, borderStyle: .roundedRect) + passwordField?.textContentType = .password + + focusTextFields = [ usernameField!, passwordField! ] + + let hostView = UIView() + hostView.translatesAutoresizingMaskIntoConstraints = false + + hostView.embedVertically(views: [usernameField!, passwordField!], insets: .zero, spacingProvider: { leadingView, trailingView in + return 10 + }, centered: false) + + contentView = hostView + } else { + contentView = certificateSummaryView + } + + updateState() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + guard case let .authenticate(withCredentials: withCredentials, username: _, password: _) = step else { + return + } + + if withCredentials { + usernameField?.becomeFirstResponder() + } + } + + func updateState() { + guard case let .authenticate(withCredentials: withCredentials, username: _, password: _) = step else { + return + } + + if withCredentials { + if let username = usernameField?.text, username.count > 0, let password = usernameField?.text, password.count > 0 { + continueButton.isEnabled = true + } else { + continueButton.isEnabled = false + } + } + } + + override func handleContinue() { + setupViewController?.composer?.authenticate(username: usernameField?.text, password: passwordField?.text, presentingViewController: self, completion: composerCompletion) + } +} diff --git a/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterURLViewController.swift b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterURLViewController.swift new file mode 100644 index 000000000..b06dd60a4 --- /dev/null +++ b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterURLViewController.swift @@ -0,0 +1,61 @@ +// +// BookmarkSetupStepEnterURLViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 06.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudAppShared + +class BookmarkSetupStepEnterURLViewController: BookmarkSetupStepViewController { + var urlTextField: UITextField? + + override func loadView() { + stepTitle = "Server URL".localized + + super.loadView() + + urlTextField = buildTextField(withAction: UIAction(handler: { [weak self] _ in + self?.updateState() + }), placeholder: "https://", keyboardType: .URL, autocorrectionType: .no, autocapitalizationType: .none, accessibilityLabel: "Server URL".localized, borderStyle: .roundedRect) + + urlTextField?.text = setupViewController?.composer?.configuration.url?.absoluteString + + focusTextFields = [ urlTextField! ] + + contentView = urlTextField + + updateState() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + urlTextField?.becomeFirstResponder() + } + + func updateState() { + if let urlString = urlTextField?.text, urlString.count > 0, NSURL(username: nil, password: nil, afterNormalizingURLString: urlString, protocolWasPrepended: nil) != nil { + continueButton.isEnabled = true + } else { + continueButton.isEnabled = false + } + } + + override func handleContinue() { + if let urlString = urlTextField?.text { + setupViewController?.composer?.enterURL(urlString, completion: composerCompletion) + } + } +} diff --git a/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterUsernameViewController.swift b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterUsernameViewController.swift new file mode 100644 index 000000000..0b2af7d57 --- /dev/null +++ b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepEnterUsernameViewController.swift @@ -0,0 +1,59 @@ +// +// BookmarkSetupStepEnterUsernameViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 06.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +class BookmarkSetupStepEnterUsernameViewController: BookmarkSetupStepViewController { + var usernameField: UITextField? + + override func loadView() { + stepTitle = "Username".localized + + super.loadView() + + usernameField = buildTextField(withAction: UIAction(handler: { [weak self] _ in + self?.updateState() + }), autocorrectionType: .no, autocapitalizationType: .none, accessibilityLabel: "Username".localized, borderStyle: .roundedRect) + usernameField?.textContentType = .username + + focusTextFields = [ usernameField! ] + + contentView = usernameField + + updateState() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + usernameField?.becomeFirstResponder() + } + + func updateState() { + if let username = usernameField?.text, username.count > 0 { + continueButton.isEnabled = true + } else { + continueButton.isEnabled = false + } + } + + override func handleContinue() { + if let username = usernameField?.text { + setupViewController?.composer?.enterUsername(username, completion: composerCompletion) + } + } +} diff --git a/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepFinishedViewController.swift b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepFinishedViewController.swift new file mode 100644 index 000000000..a2a48b2f4 --- /dev/null +++ b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepFinishedViewController.swift @@ -0,0 +1,58 @@ +// +// BookmarkSetupStepFinishedViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 06.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit + +class BookmarkSetupStepFinishedViewController: BookmarkSetupStepViewController { + var bookmarkNameField: UITextField? + + override func loadView() { + stepTitle = "Account setup complete".localized + + if setupViewController?.configuration.nameEditable == true { + stepMessage = "If you'd like to give the account a custom name, please enter it below:".localized + } + + continueButtonLabelText = "Done".localized + + super.loadView() + + if setupViewController?.configuration.nameEditable == true { + bookmarkNameField = buildTextField(withAction: UIAction(handler: { [weak self] _ in + self?.updateName() + }), placeholder: setupViewController?.composer?.bookmark.shortName ?? "Name", value: setupViewController?.composer?.bookmark.name ?? "", autocorrectionType: .no, autocapitalizationType: .none, accessibilityLabel: "Name".localized, borderStyle: .roundedRect) + + contentView = bookmarkNameField + } + } + + func updateName() { + var name = bookmarkNameField?.text + + if name != nil, name?.count == 0 { + name = nil + } + + setupViewController?.composer?.setName(name) + } + + override func handleContinue() { + let bookmark = setupViewController?.composer?.addBookmark() + setupViewController?.done(bookmark: bookmark) + } +} diff --git a/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepIntroViewController.swift b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepIntroViewController.swift new file mode 100644 index 000000000..17f2afb8e --- /dev/null +++ b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepIntroViewController.swift @@ -0,0 +1,59 @@ +// +// BookmarkSetupStepIntroViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 11.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudAppShared + +class BookmarkSetupStepIntroViewController: BookmarkSetupStepViewController { + var hasSettings: Bool { + return setupViewController?.configuration.hasSettings == true + } + + override func loadView() { + super.loadView() + + continueButtonLabelText = "Start setup".localized + + if hasSettings { + backButtonLabelText = "Settings".localized + } + + let messageView = ComposedMessageView(elements: [ + .title(String(format: "Welcome to %@".localized, VendorServices.shared.appName), alignment: .centered, cssSelectors: [.title], insets: NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 0)), + .subtitle("The following steps will guide you through the setup process.".localized, alignment: .centered, cssSelectors: [.message]) + ]) + messageView.elementInsets = .zero + messageView.cssSelectors = [ .welcome ] + + contentView = messageView + } + + override var hasBackButton: Bool { + return hasSettings + } + + override func handleContinue() { + setupViewController?.composer?.doneIntro() + } + + override func handleBack() { + let navigationViewController = ThemeNavigationController(rootViewController: SettingsViewController()) + navigationViewController.modalPresentationStyle = .fullScreen + present(navigationViewController, animated: true) + } +} diff --git a/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepPrepopulateViewController.swift b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepPrepopulateViewController.swift new file mode 100644 index 000000000..400d45b1f --- /dev/null +++ b/ownCloud/Bookmarks/Setup/Steps/BookmarkSetupStepPrepopulateViewController.swift @@ -0,0 +1,102 @@ +// +// BookmarkSetupStepPrepopulateViewController.swift +// ownCloud +// +// Created by Felix Schwarz on 06.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudAppShared + +class BookmarkSetupStepProgressViewController: BookmarkSetupStepViewController { + open var cancelled : Bool = false + + open var progressView : ProgressView + + var progressMessageObservation : NSKeyValueObservation? + var progressValueObservation : NSKeyValueObservation? + open var progress : Progress? { + willSet { + progressMessageObservation?.invalidate() + progressMessageObservation = nil + + progressView.progress = nil + } + + didSet { + if progress != nil { + progressMessageObservation = progress?.observe(\Progress.localizedDescription, options: NSKeyValueObservingOptions.initial, changeHandler: { [weak self] progress, _ in + OnMainThread { + self?.messageLabel?.text = progress.localizedDescription + } + }) + + progressView.progress = progress + } + } + } + + public override init(with setupViewController: BookmarkSetupViewController, step: BookmarkComposer.Step) { + progressView = ProgressView() + progressView.translatesAutoresizingMaskIntoConstraints = false + + let indeterminateProgress = Progress.indeterminate() + indeterminateProgress.isCancellable = false + progressView.progress = indeterminateProgress + + super.init(with: setupViewController, step: step) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override open func viewDidLoad() { + super.viewDidLoad() + self.contentView = progressView + } + + override func handleContinue() { + if !self.cancelled { + self.handleCancellation() + } + + self.cancelled = true + } + + func handleCancellation() { + // Subclassing point + } +} + +class BookmarkSetupStepPrepopulateViewController: BookmarkSetupStepProgressViewController { + public override init(with setupViewController: BookmarkSetupViewController, step: BookmarkComposer.Step) { + super.init(with: setupViewController, step: step) + + self.stepTitle = "Preparing account".localized + self.stepMessage = "Please wait…".localized + + self.continueButtonLabelText = "Skip".localized + + self.progress = setupViewController.composer?.prepopulate(completion: self.composerCompletion) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func handleCancellation() { + self.progress?.cancel() + } +} diff --git a/ownCloud/Bookmarks/Setup/Steps/CertificateSummaryView.swift b/ownCloud/Bookmarks/Setup/Steps/CertificateSummaryView.swift new file mode 100644 index 000000000..a09127e88 --- /dev/null +++ b/ownCloud/Bookmarks/Setup/Steps/CertificateSummaryView.swift @@ -0,0 +1,128 @@ +// +// CertificateSummaryView.swift +// ownCloud +// +// Created by Felix Schwarz on 06.09.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK +import ownCloudApp +import ownCloudAppShared + +class CertificateSummaryView: ThemeCSSView { + init(with certificate: OCCertificate?, httpHostname: String?) { + super.init() + + cssSelector = .certificateSummary + + button.translatesAutoresizingMaskIntoConstraints = false + embed(toFillWith: button) + + button.addAction(UIAction(handler: { [weak self] _ in + self?.showCertificate() + }), for: .primaryActionTriggered) + + OnMainThread { + self.httpHostname = httpHostname + self.certificate = certificate + + self.update() + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var certificate: OCCertificate? { + didSet { + if let certificate { + certificate.validationResult(completionHandler: { (validationResult, shortDescription, _, color, _) in + OnMainThread { + self.validationResult = validationResult + self.statusText = shortDescription + self.statusColor = color + + self.update() + } + }) + } else { + OnMainThread { + self.validationResult = nil + self.statusText = nil + self.statusColor = .systemRed + + self.update() + } + } + } + } + + var httpHostname: String? + + var button: UIButton = UIButton() + + var validationResult: OCCertificateValidationResult? + var statusText: String? + var statusColor: UIColor? + + var statusImage: UIImage? { + var imageName: String? + + if certificate != nil { + if let validationResult { + + switch validationResult { + case .none, .error, .reject, .promptUser: + imageName = "lock.slash.fill" + + case .passed: + imageName = "lock.fill" + + case .userAccepted: + imageName = "exclamationmark.lock.fill" + } + } + } else { + imageName = "lock.open.fill" + } + + if let imageName { + return UIImage.init(systemName: imageName) + } + + return nil + } + + func update() { + var buttonConfiguration : UIButton.Configuration = .borderless() + buttonConfiguration.baseForegroundColor = statusColor + buttonConfiguration.baseBackgroundColor = .clear + buttonConfiguration.image = self.statusImage + buttonConfiguration.title = certificate?.hostName ?? httpHostname + buttonConfiguration.buttonSize = .mini + + button.configuration = buttonConfiguration + } + + func showCertificate() { + if let certificate { + let certificateViewController : ThemeCertificateViewController = ThemeCertificateViewController(certificate: certificate, compare: nil) + let navigationController = ThemeNavigationController(rootViewController: certificateViewController) + + hostingViewController?.present(navigationController, animated: true, completion: nil) + } + } +} diff --git a/ownCloud/Client/Actions/Actions+Extensions/CreateDocumentAction.swift b/ownCloud/Client/Actions/Actions+Extensions/CreateDocumentAction.swift index a7c832f63..2814c1cf4 100644 --- a/ownCloud/Client/Actions/Actions+Extensions/CreateDocumentAction.swift +++ b/ownCloud/Client/Actions/Actions+Extensions/CreateDocumentAction.swift @@ -270,6 +270,7 @@ class CreateDocumentAction: Action { } }) + documentNameViewController.requiredFileExtension = fileType.extension documentNameViewController.navigationItem.title = "Pick a name".localized navigationViewController.pushViewController(documentNameViewController, animated: true) diff --git a/ownCloud/Client/Actions/EditDocumentViewController.swift b/ownCloud/Client/Actions/EditDocumentViewController.swift index 0553f4cba..a0f77fa37 100644 --- a/ownCloud/Client/Actions/EditDocumentViewController.swift +++ b/ownCloud/Client/Actions/EditDocumentViewController.swift @@ -154,10 +154,24 @@ class EditDocumentViewController: QLPreviewController, Themeable { } } } + + func disableEditingMode() { + if #available(iOS 17.0, *) { + if let rightBarButtonItems = self.navigationItem.rightBarButtonItems, rightBarButtonItems.count > 0 { + for markupButton in rightBarButtonItems { + if (markupButton.debugDescription as NSString).contains("pencil.tip.crop.circle") { + _ = markupButton.target?.perform(markupButton.action, with: markupButton) + } + } + } + } else { + self.setEditing(false, animated: false) + } + } @objc func dismissAnimated() { - self.setEditing(false, animated: false) - + disableEditingMode() + if savingMode == nil { requestsavingMode { (savingMode) in self.dismiss(animated: true) { diff --git a/ownCloud/Client/Actions/Scanner/ScanViewController.swift b/ownCloud/Client/Actions/Scanner/ScanViewController.swift index 43853dec8..24d4e4288 100644 --- a/ownCloud/Client/Actions/Scanner/ScanViewController.swift +++ b/ownCloud/Client/Actions/Scanner/ScanViewController.swift @@ -287,6 +287,11 @@ class ScanViewController: StaticTableViewController { didSet { if let fileName = fileNameRow?.value as? NSString { fileNameRow?.value = fileName.deletingPathExtension + "." + (exportFormat?.suffix ?? "") + fileNameRow?.requiredFileExtension = exportFormat?.suffix + + if let textField = fileNameRow?.textField, textField.isFirstResponder { + textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.beginningOfDocument) + } } guard let oneFilePerPageRow = oneFilePerPageRow else { return } @@ -315,7 +320,9 @@ class ScanViewController: StaticTableViewController { self.isModalInPresentation = true self.navigationItem.title = "Scan".localized self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(ScanViewController.cancel)) - self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(ScanViewController.save)) + + let saveBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(ScanViewController.save)) + self.navigationItem.rightBarButtonItem = saveBarButtonItem self.core = core self.targetFolderItem = item @@ -353,7 +360,7 @@ class ScanViewController: StaticTableViewController { // Save section // - Name - fileNameRow = StaticTableViewRow(textFieldWithAction: { [weak self] (row, textField, type) in + fileNameRow = StaticTableViewRow(textFieldWithAction: { [weak self, weak saveBarButtonItem] (row, textField, type) in self?.navigationItem.rightBarButtonItem?.isEnabled = ((row.value as? String)?.count ?? 0) > 0 if type == .didBegin, let nameTextField = textField as? UITextField { @@ -369,6 +376,10 @@ class ScanViewController: StaticTableViewController { nameTextField.selectedTextRange = nameTextField.textRange(from: nameTextField.beginningOfDocument, to: nameTextField.endOfDocument) } } + + if let fileName = (textField as? UITextField)?.text, let requiredFileExtension = row.requiredFileExtension { + saveBarButtonItem?.isEnabled = (fileName.count > (requiredFileExtension.count + 1)) + } }, placeholder: "Name".localized, value: fileName ?? "", keyboardType: .default, autocorrectionType: .no, enablesReturnKeyAutomatically: true, returnKeyType: .default, identifier: "name", accessibilityLabel: "Name".localized) saveSection.add(row: fileNameRow!) self.addSection(saveSection) diff --git a/ownCloud/Client/Viewer/DisplayHostViewController.swift b/ownCloud/Client/Viewer/DisplayHostViewController.swift index f13a59aef..165f250ce 100644 --- a/ownCloud/Client/Viewer/DisplayHostViewController.swift +++ b/ownCloud/Client/Viewer/DisplayHostViewController.swift @@ -176,13 +176,13 @@ class DisplayHostViewController: UIPageViewController { } // MARK: - Active Viewer - private var _navigationTitleObservation : NSKeyValueObservation? + private var _navigationItemObservation : NSKeyValueObservation? private var _navigationBarButtonItemsObservation : NSKeyValueObservation? weak var activeDisplayViewController : DisplayViewController? { willSet { - _navigationTitleObservation?.invalidate() - _navigationTitleObservation = nil + _navigationItemObservation?.invalidate() + _navigationItemObservation = nil _navigationBarButtonItemsObservation?.invalidate() _navigationBarButtonItemsObservation = nil @@ -191,8 +191,12 @@ class DisplayHostViewController: UIPageViewController { didSet { Log.debug("New activeDisplayViewController: \(activeDisplayViewController?.item?.name ?? "-")") - _navigationTitleObservation = activeDisplayViewController?.observe(\DisplayViewController.displayTitle, options: .initial, changeHandler: { [weak self] (displayViewController, _) in - self?.navigationItem.title = displayViewController.displayTitle + _navigationItemObservation = activeDisplayViewController?.observe(\DisplayViewController.item, options: .initial, changeHandler: { [weak self] (displayViewController, _) in + if let itemLocation = displayViewController.item?.location, let clientContext = displayViewController.clientContext { + OnMainThread(inline: true) { + self?.navigationItem.titleView = ClientLocationPopupButton(clientContext: clientContext, location: itemLocation) + } + } }) _navigationBarButtonItemsObservation = activeDisplayViewController?.observe(\DisplayViewController.displayBarButtonItems, options: .initial, changeHandler: { [weak self] (displayViewController, _) in diff --git a/ownCloud/Client/Viewer/DisplayViewController.swift b/ownCloud/Client/Viewer/DisplayViewController.swift index 86008909f..55a333bd5 100644 --- a/ownCloud/Client/Viewer/DisplayViewController.swift +++ b/ownCloud/Client/Viewer/DisplayViewController.swift @@ -120,7 +120,7 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate { return nil } - var item: OCItem? { + @objc dynamic var item: OCItem? { didSet { if itemClaimIdentifier == nil, // No claim registered by the DisplayViewController for the item yet let item = item, let core = core, @@ -477,8 +477,8 @@ class DisplayViewController: UIViewController, Themeable, OCQueryDelegate { } // MARK: - UI management - @objc var displayTitle : String? - @objc var displayBarButtonItems : [UIBarButtonItem]? + @objc dynamic var displayTitle : String? + @objc dynamic var displayBarButtonItems : [UIBarButtonItem]? private func updateDisplayTitleAndButtons() { if let itemName = item?.name { diff --git a/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift b/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift index debb8ada9..9e6bf5a56 100644 --- a/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift +++ b/ownCloud/Client/Viewer/Image/ImageDisplayViewController.swift @@ -220,6 +220,13 @@ extension ImageDisplayViewController: DisplayExtension { extension ImageDisplayViewController: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer == tapToZoomGestureRecognizer, + scrollView?.hasActiveImageAnalysisSelection == true, // allow selection when VisionKit image analysis is active + let otherGestureRecognizer = otherGestureRecognizer as? UITapGestureRecognizer, + otherGestureRecognizer.numberOfTapsRequired == 2 { + return true + } + if gestureRecognizer === tapToZoomGestureRecognizer && otherGestureRecognizer === showHideBarsTapGestureRecognizer { return true } diff --git a/ownCloud/Licensing/Offers/LicenseOfferView.swift b/ownCloud/Licensing/Offers/LicenseOfferView.swift index 4e43924ee..b8e8e1808 100644 --- a/ownCloud/Licensing/Offers/LicenseOfferView.swift +++ b/ownCloud/Licensing/Offers/LicenseOfferView.swift @@ -204,7 +204,7 @@ class LicenseOfferView: UIView, Themeable { if let purchaseButton = purchaseButton { let progress = Progress.indeterminate() - progress?.isCancellable = false + progress.isCancellable = false purchaseBusyView = ProgressView() purchaseBusyView?.translatesAutoresizingMaskIntoConstraints = false diff --git a/ownCloud/Release Notes/ReleaseNotes.plist b/ownCloud/Release Notes/ReleaseNotes.plist index 717842625..a1dd56af1 100644 --- a/ownCloud/Release Notes/ReleaseNotes.plist +++ b/ownCloud/Release Notes/ReleaseNotes.plist @@ -1788,6 +1788,123 @@ Added an optional "Wait for completion" option to the "Save File& + + Version + 12.1 + ReleaseNotes + + + Title + New account wizard + Subtitle + Introducing a new, intuitive account wizard user interface for a fast onboarding experience. + Type + New + ImageName + sparkles.rectangle.stack + + + Title + Reordering accounts + Subtitle + Now you can easily reorder accounts in the sidebar by simply dragging and dropping them. + Type + New + ImageName + line.3.horizontal.circle + + + Title + Location breadcrumb + Subtitle + We have added a location breadcrumb dropdown in the file viewer, making it easier to navigate through your files. + Type + New + ImageName + chevron.down + + + Title + Text recognition + Subtitle + You can now perform text recognition actions on images. + Type + New + ImageName + text.viewfinder + + + Title + File extension / suffix protection + Subtitle + To prevent accidental removal or modification of the suffix for new documents and document scanner files, we have implemented file extension protection. + Type + New + ImageName + lock.shield + + + Title + Share Action "Save to ownCloud" + Subtitle + We have added a new action called "Save to ownCloud" to the share sheet, allowing you to easily save files to your ownCloud account. + Type + New + ImageName + square.and.arrow.up + + + Title + Link naming + Subtitle + You can now enter and edit the name of link shares. + Type + New + ImageName + character.textbox + + + Title + Sort by Last Used + Subtitle + Introducing a new sorting method - “Last Used” - which allows you to sort items based on the most recently accessed ones. + Type + New + ImageName + arrow.up.arrow.down + + + Title + Markup Edit Mode on iOS 17 + Subtitle + We have fixed the issue that was disabling the edit mode in the markup document view on iOS 17. + Type + Fix + ImageName + wrench + + + Title + Open ownCloud Links in App + Subtitle + We have resolved the issue with opening private links from third-party apps. + Type + Fix + ImageName + wrench + + + Title + Bug Fixes + Subtitle + We have addressed various issues with the File Provider and the app itself. + Type + Fix + ImageName + wrench + + + diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-1024x1024.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-1024x1024.png deleted file mode 100644 index 2bddda788..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-1024x1024.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-120x120.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-120x120.png deleted file mode 100644 index 15322073b..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-120x120.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-152x152.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-152x152.png deleted file mode 100644 index dbc6b8aab..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-152x152.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-167x167.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-167x167.png deleted file mode 100644 index a026e7e48..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-167x167.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-180x180.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-180x180.png deleted file mode 100644 index bb66bbb03..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-180x180.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-20x20.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-20x20.png deleted file mode 100644 index 6bdb42926..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-20x20.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-29x29.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-29x29.png deleted file mode 100644 index edabadab0..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-29x29.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-40x40.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-40x40.png deleted file mode 100644 index 4f1613797..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-40x40.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-58x58.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-58x58.png deleted file mode 100644 index 8e2e27749..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-58x58.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-60x60.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-60x60.png deleted file mode 100644 index 475cc6d88..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-60x60.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-76x76.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-76x76.png deleted file mode 100644 index 0b8a3ec7a..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-76x76.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-80x80.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-80x80.png deleted file mode 100644 index 0c29dcc18..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-80x80.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-87x87.png b/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-87x87.png deleted file mode 100644 index eca51a9c0..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024-87x87.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/Contents.json b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/Contents.json deleted file mode 100644 index b75f2d40f..000000000 --- a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom": "iphone", - "filename" : "icon-20@2x.png", - "scale": "2x" - }, - { - "size" : "20x20", - "idiom": "iphone", - "filename" : "icon-20@3x.png", - "scale": "3x" - }, - { - "size" : "20x20", - "idiom": "ipad", - "filename" : "icon-20.png", - "scale": "1x" - }, - { - "size" : "20x20", - "idiom": "ipad", - "filename" : "icon-20@2x.png", - "scale": "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-60@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "icon-83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "icon-1024.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-1024.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-1024.png deleted file mode 100644 index 095f8d70b..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-1024.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20.png deleted file mode 100644 index 6507f8f4f..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20@2x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20@2x.png deleted file mode 100644 index aac4be424..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20@3x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20@3x.png deleted file mode 100644 index baad7533b..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-20@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29.png deleted file mode 100644 index 0d0fd6686..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29@2x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29@2x.png deleted file mode 100644 index 88932ee8a..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29@3x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29@3x.png deleted file mode 100644 index 6f3a6966b..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-29@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40.png deleted file mode 100644 index aac4be424..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40@2x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40@2x.png deleted file mode 100644 index 110363ec8..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40@3x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40@3x.png deleted file mode 100644 index 0b5cc5a7b..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-40@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-60@2x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-60@2x.png deleted file mode 100644 index 0b5cc5a7b..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-60@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-60@3x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-60@3x.png deleted file mode 100644 index d6cabc294..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-60@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-76.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-76.png deleted file mode 100644 index 36756000c..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-76.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-76@2x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-76@2x.png deleted file mode 100644 index 47b5f048e..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-76@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-83.5@2x.png b/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-83.5@2x.png deleted file mode 100644 index 5ddd010df..000000000 Binary files a/ownCloud/Resources/Assets.xcassets/iOS/AppIcon.appiconset/icon-83.5@2x.png and /dev/null differ diff --git a/ownCloud/Resources/LaunchScreen.storyboard b/ownCloud/Resources/LaunchScreen.storyboard index 3b88ec199..8e974e8bb 100644 --- a/ownCloud/Resources/LaunchScreen.storyboard +++ b/ownCloud/Resources/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -19,10 +19,11 @@ - - + + + @@ -41,7 +42,6 @@ - @@ -51,6 +51,6 @@ - + diff --git a/ownCloud/Resources/Theming/branding-assets-input.xcfilelist b/ownCloud/Resources/Theming/branding-assets-input.xcfilelist new file mode 100644 index 000000000..3cfca7dc3 --- /dev/null +++ b/ownCloud/Resources/Theming/branding-assets-input.xcfilelist @@ -0,0 +1,7 @@ +# List of all possible source locations + +# Default theme +${PROJECT_DIR}/ownCloud/Resources/Theming/com.owncloud.ios-app + +# branding-assets +${PROJECT_DIR}/ownCloud/Resources/Theming/branding-assets \ No newline at end of file diff --git a/ownCloud/Resources/Theming/branding-assets-output.xcfilelist b/ownCloud/Resources/Theming/branding-assets-output.xcfilelist new file mode 100644 index 000000000..00dd41eab --- /dev/null +++ b/ownCloud/Resources/Theming/branding-assets-output.xcfilelist @@ -0,0 +1,24 @@ +# List of all possible files in branding-assets (target locations) + +# BrandView assets +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-logo.png +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-background.png + +# - setup variants +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-logo-setup.png +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-background-setup.png + +# - sidebar variants +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-logo-sidebar.png +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-background-sidebar.png + +# - legacy paths +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-login-logo.png +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-login-background.png + +# Splashscreen assets +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-splashscreen-logo.png +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-splashscreen-background.png + +# Sidebar link icon +${TARGET_BUILD_DIR}/${WRAPPER_NAME}/branding-sidebar-link-icon.png diff --git a/ownCloud/Resources/Theming/branding-assets/.do-not-remove b/ownCloud/Resources/Theming/branding-assets/.do-not-remove new file mode 100644 index 000000000..e69de29bb diff --git a/ownCloud/Resources/Theming/branding-bookmark-icon.png b/ownCloud/Resources/Theming/branding-bookmark-icon.png deleted file mode 100644 index 24d08707e..000000000 Binary files a/ownCloud/Resources/Theming/branding-bookmark-icon.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/branding-icon.png b/ownCloud/Resources/Theming/branding-icon.png deleted file mode 100644 index 66294a740..000000000 Binary files a/ownCloud/Resources/Theming/branding-icon.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/branding-login-background.png b/ownCloud/Resources/Theming/branding-login-background.png deleted file mode 100644 index 52bd98b44..000000000 Binary files a/ownCloud/Resources/Theming/branding-login-background.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/branding-splashscreen-background.png b/ownCloud/Resources/Theming/branding-splashscreen-background.png deleted file mode 100644 index ca2b68fcf..000000000 Binary files a/ownCloud/Resources/Theming/branding-splashscreen-background.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/Contents.json b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 770379d94..000000000 --- a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "icon-20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "icon-20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "icon-20.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "icon-20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "icon-83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "icon-1024.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20.png deleted file mode 100644 index 00f48a8db..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png deleted file mode 100644 index 415be8917..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png deleted file mode 100644 index e06a34160..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29.png deleted file mode 100644 index 10d288c08..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png deleted file mode 100644 index c7009e370..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png deleted file mode 100644 index d1d090968..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40.png deleted file mode 100644 index 415be8917..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png deleted file mode 100644 index 46621e254..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png deleted file mode 100644 index 90f20f466..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png deleted file mode 100644 index 90f20f466..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png deleted file mode 100644 index 060f21f82..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-76.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-76.png deleted file mode 100644 index e50667f14..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-76.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png deleted file mode 100644 index 104d696f2..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png deleted file mode 100644 index fed0a2898..000000000 Binary files a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-background.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-background.png new file mode 100644 index 000000000..db7653966 Binary files /dev/null and b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-background.png differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-icon.png similarity index 100% rename from ownCloud/Resources/Theming/com.owncloud.ios-app.emm/Assets.xcassets/AppIcon.appiconset/icon-1024.png rename to ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-icon.png diff --git a/ownCloud/Resources/Theming/branding-login-logo.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-logo.png similarity index 100% rename from ownCloud/Resources/Theming/branding-login-logo.png rename to ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-logo.png diff --git a/ownCloud/Resources/Theming/branding-sidebar-link-icon.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-sidebar-link-icon.png similarity index 100% rename from ownCloud/Resources/Theming/branding-sidebar-link-icon.png rename to ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-sidebar-link-icon.png diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-splashscreen-background.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-splashscreen-background.png new file mode 100644 index 000000000..d07c4dbf6 Binary files /dev/null and b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-splashscreen-background.png differ diff --git a/ownCloud/Resources/Theming/branding-splashscreen.png b/ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-splashscreen-logo.png similarity index 100% rename from ownCloud/Resources/Theming/branding-splashscreen.png rename to ownCloud/Resources/Theming/com.owncloud.ios-app.emm/branding-splashscreen-logo.png diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-background.png b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-background.png new file mode 100644 index 000000000..db7653966 Binary files /dev/null and b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-background.png differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-icon.png b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-icon.png new file mode 100644 index 000000000..33d9e8b6d Binary files /dev/null and b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-icon.png differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-logo.png b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-logo.png new file mode 100644 index 000000000..791a812af Binary files /dev/null and b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-logo.png differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-sidebar-link-icon.png b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-sidebar-link-icon.png new file mode 100644 index 000000000..791a812af Binary files /dev/null and b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-sidebar-link-icon.png differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-splashscreen-background.png b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-splashscreen-background.png new file mode 100644 index 000000000..d07c4dbf6 Binary files /dev/null and b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-splashscreen-background.png differ diff --git a/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-splashscreen-logo.png b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-splashscreen-logo.png new file mode 100644 index 000000000..791a812af Binary files /dev/null and b/ownCloud/Resources/Theming/com.owncloud.ios-app/branding-splashscreen-logo.png differ diff --git a/ownCloud/Resources/Theming/generate.sh b/ownCloud/Resources/Theming/generate.sh new file mode 100755 index 000000000..9059cf5ae --- /dev/null +++ b/ownCloud/Resources/Theming/generate.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# PREREQUISITE +# Create symlink +# ln -s SYNCDIR\ Config\ Data/5500Z000003poZfQAI/ios/current ~/Developer/github.com/owncloud/ios-app/ownCloud/Resources/Theming/theme.damken + +current_dir=$(pwd) +theming_dir="/ownCloud/Resources/Theming" +theme=$1 + +if [ -d "$theming_dir" ]; then + git submodule init + git submodule update + + rename 's/current./theme./' $current_dir$theming_dir/*.* + mv $current_dir$theming_dir/theme.$theme $current_dir$theming_dir/current.$theme + + cp $current_dir$theming_dir/current*/*.png $current_dir$theming_dir/ + cp $current_dir$theming_dir/current*/*.json $current_dir$theming_dir/Branding.json + + fastlane generate_appicon + + gomplate --file ./tools/gomplate/Branding.plist.tmpl \ + --context config=$current_dir$theming_dir/Branding.json \ + --out $current_dir$theming_dir/Branding.plist +else + echo "Directory $theming_dir does not exist. Please execute this script in the root path of the ios-app repository." +fi \ No newline at end of file diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/Contents.json b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 770379d94..000000000 --- a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "icon-20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "icon-20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "icon-29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "icon-40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "icon-60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "icon-20.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "icon-20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "icon-29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "icon-40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "icon-76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "icon-83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "icon-1024.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20.png deleted file mode 100644 index 8efbb519b..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png deleted file mode 100644 index dad04e3d9..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png deleted file mode 100644 index 5d70c3675..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29.png deleted file mode 100644 index bac72d87b..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png deleted file mode 100644 index f9a9652e5..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png deleted file mode 100644 index c78bfbeb0..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40.png deleted file mode 100644 index dad04e3d9..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png deleted file mode 100644 index 3d269139d..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png deleted file mode 100644 index 3afa83291..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png deleted file mode 100644 index 3afa83291..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png deleted file mode 100644 index b3ca62124..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-76.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-76.png deleted file mode 100644 index 15b54ed2d..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-76.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png deleted file mode 100644 index 6b7f351df..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png deleted file mode 100644 index 41fa57b3d..000000000 Binary files a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png and /dev/null differ diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Branding.plist b/ownCloud/Resources/Theming/online.owncloud.ios-app/Branding.plist index a75004c80..5c3d47477 100644 --- a/ownCloud/Resources/Theming/online.owncloud.ios-app/Branding.plist +++ b/ownCloud/Resources/Theming/online.owncloud.ios-app/Branding.plist @@ -10,598 +10,53 @@ branding.organization-name ownCloud Online - branding.send-feedback-address - branding.profile-definitions + allowedHosts + + owncloud.online + owncloud.com + owncloud.team + owncloud.works + owncloud.org + + bookmarkName + ownCloud Online + canConfigureURL + + helpURL + https://owncloud.online/try/ + helpURLButtonString + TRY IT FOR FREE identifier ownCloud-online name ownCloud Online - promptForURL - Please enter your ownCloud Online URL promptForHelpURL If you do not have an ownCloud Online account yet, you can request a free test account. - helpURLButtonString - TRY IT FOR FREE - bookmarkName - ownCloud Online + promptForURL + Please enter your ownCloud Online URL url - canConfigureURL - - helpURL - https://owncloud.online/try/ - allowedHosts - - owncloud.online - owncloud.com - owncloud.team - - allowedAuthenticationMethods - - com.owncloud.basicauth - com.owncloud.oauth2 - + branding.send-feedback-address + + branding.theme-colors + + branding-background-color + #628a99 + setup-status-bar-style + black + folder-icon-color + #52DDA9 + file-icon-color + #52DDA9 + branding.url-help https://owncloud.online/faq/ branding.url-privacy https://owncloud.online/privacy-policy/ - branding.theme-generic-colors - - Corporate - - Color - #52DDA9 - - Action - - Primary - #1279C5 - Inverse - #FFFFFF - Secondary - #777777 - - Text - - Primary - #333333 - Inverse - #000000 - Secondary - #666666 - Inverse-Light - #000000 - Disabled - #BFC9D9 - - Login - - Primary - #333333 - Inverse - #FFFFFF - Secondary - #666666 - Inverse-Light - #F2F4F7 - Disabled - #BFC9D9 - - Background - - Standard - #FFFFFF - Light - #EEEEEE - Input-Form - #FFFFFF - Selected - #E5EFFF - Tooltip - #1F2939 - - System - - success - #4CD964 - success-background - #DBF7E0 - warning - #FF8800 - warning-background - #FFE7CC - danger - #E00000 - danger-background - #F9CCCC - - Status - - Progress-Indicator - #7357C7 - Progress-Background - #D5D3DC - - Icon - - System - #667FA3 - System-Light - #CCD4E0 - Filetype-Normal - #00af90 - Disabled - #C4C4C4 - - Tab - - Active - #00af90 - Inactive - #777777 - - - branding.theme-definitions - - - Name - ownCloud-online - Identifier - com.owncloud.branding - ThemeStyle - contrast - darkBrandColor - #52DDA9 - lightBrandColor - #628a99 - Colors - - tintColor - #000000 - Label - - informativeColor - - successColor - System.Success - warningColor - System.Warning - errorColor - System.Danger - - Fill - - approvalColors - - normal - - background - #52DDA9 - foreground - - - highlighted - - background - #52DDA9 - foreground - - - disabled - - background - #37C390 - foreground - - - - neutralColors - - normal - - background - - foreground - - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - destructiveColors - - normal - - background - - foreground - - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - Favorite - - enabledColor - - disabledColor - - - Table - - tableBackgroundColor - Background.Standard - tableGroupBackgroundColor - - tableSeparatorColor - - tableRowBorderColor - - tableRowColors - - backgroundColor - Background.Standard - labelColor - Text.Primary - secondaryLabelColor - Text.Secondary - symbolColor - Action.Primary - tintColor - - filledColorPairCollection - - normal - - background - - foreground - - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - tableRowHighlightColors - - backgroundColor - #DCEBF6 - labelColor - - secondaryLabelColor - - symbolColor - - tintColor - - filledColorPairCollection - - normal - - background - - foreground - - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - - Icon - - folderFillColor - Icon.Filetype-Normal - fileFillColor - Icon.Filetype-Normal - logoFillColor - Icon.Filetype-Normal - iconFillColor - Icon.Filetype-Normal - symbolFillColor - Icon.Filetype-Normal - - Progress - - foreground - Progress.Indicator - background - Progress.Background - - Toolbar - - backgroundColor - Background.Standard - labelColor - Text.Inverse - secondaryLabelColor - Tab.Inactive - symbolColor - - tintColor - Tab.Active - filledColorPairCollection - - normal - - background - - foreground - Tab.Inactive - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - Searchbar - - backgroundColor - Background.Input-Form - labelColor - Text.Inverse - secondaryLabelColor - Text.Secondary - symbolColor - - tintColor - Text.Inverse - filledColorPairCollection - - normal - - background - - foreground - - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - Login - - backgroundColor - Background.Standard - labelColor - Login.Primary - secondaryLabelColor - Login.Secondary - symbolColor - - tintColor - Login.Inverse - filledColorPairCollection - - normal - - background - - foreground - #000000 - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - NavigationBar - - backgroundColor - #FFFFFF - labelColor - Text.Inverse - secondaryLabelColor - - symbolColor - - tintColor - Text.Inverse-Light - filledColorPairCollection - - normal - - background - - foreground - - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - darkBrandColors - - backgroundColor - - labelColor - - secondaryLabelColor - - symbolColor - - tintColor - - filledColorPairCollection - - normal - - background - - foreground - - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - lightBrandColors - - backgroundColor - - labelColor - - secondaryLabelColor - - symbolColor - - tintColor - - filledColorPairCollection - - normal - - background - - foreground - - - highlighted - - background - - foreground - - - disabled - - background - - foreground - - - - - - Styles - - statusBarStyle - darkContent - barStyle - black - activityIndicatorViewStyle - gray - searchBarActivityIndicatorViewStyle - white - interfaceStyle - light - keyboardAppearance - light - backgroundBlurEffectStyle - light - - - diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/branding-login-background.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/branding-background.png similarity index 100% rename from ownCloud/Resources/Theming/online.owncloud.ios-app/branding-login-background.png rename to ownCloud/Resources/Theming/online.owncloud.ios-app/branding-background.png diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/branding-icon.png similarity index 100% rename from ownCloud/Resources/Theming/online.owncloud.ios-app/Assets.xcassets/AppIcon.appiconset/icon-1024.png rename to ownCloud/Resources/Theming/online.owncloud.ios-app/branding-icon.png diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/branding-login-logo.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/branding-logo.png similarity index 100% rename from ownCloud/Resources/Theming/online.owncloud.ios-app/branding-login-logo.png rename to ownCloud/Resources/Theming/online.owncloud.ios-app/branding-logo.png diff --git a/ownCloud/Resources/Theming/online.owncloud.ios-app/branding-splashscreen.png b/ownCloud/Resources/Theming/online.owncloud.ios-app/branding-splashscreen-logo.png similarity index 100% rename from ownCloud/Resources/Theming/online.owncloud.ios-app/branding-splashscreen.png rename to ownCloud/Resources/Theming/online.owncloud.ios-app/branding-splashscreen-logo.png diff --git a/ownCloud/Resources/ar.lproj/InfoPlist.strings b/ownCloud/Resources/ar.lproj/InfoPlist.strings index afbc56cdc..2dda65d31 100644 Binary files a/ownCloud/Resources/ar.lproj/InfoPlist.strings and b/ownCloud/Resources/ar.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/ar.lproj/Localizable.strings b/ownCloud/Resources/ar.lproj/Localizable.strings index ec56f0246..63be839a8 100644 Binary files a/ownCloud/Resources/ar.lproj/Localizable.strings and b/ownCloud/Resources/ar.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/bg_BG.lproj/InfoPlist.strings b/ownCloud/Resources/bg_BG.lproj/InfoPlist.strings index 5ee67e4b6..76efdf907 100644 Binary files a/ownCloud/Resources/bg_BG.lproj/InfoPlist.strings and b/ownCloud/Resources/bg_BG.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/bg_BG.lproj/Localizable.strings b/ownCloud/Resources/bg_BG.lproj/Localizable.strings index f6286b178..e48364fce 100644 Binary files a/ownCloud/Resources/bg_BG.lproj/Localizable.strings and b/ownCloud/Resources/bg_BG.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/de-DE.lproj/InfoPlist.strings b/ownCloud/Resources/de-DE.lproj/InfoPlist.strings index ca2233854..1b78abc6d 100644 Binary files a/ownCloud/Resources/de-DE.lproj/InfoPlist.strings and b/ownCloud/Resources/de-DE.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/de-DE.lproj/Localizable.strings b/ownCloud/Resources/de-DE.lproj/Localizable.strings index 2930d8c17..903237734 100644 Binary files a/ownCloud/Resources/de-DE.lproj/Localizable.strings and b/ownCloud/Resources/de-DE.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/de.lproj/InfoPlist.strings b/ownCloud/Resources/de.lproj/InfoPlist.strings index ca2233854..1b78abc6d 100644 Binary files a/ownCloud/Resources/de.lproj/InfoPlist.strings and b/ownCloud/Resources/de.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/de.lproj/Localizable.strings b/ownCloud/Resources/de.lproj/Localizable.strings index df7c44913..7b8e9542b 100644 Binary files a/ownCloud/Resources/de.lproj/Localizable.strings and b/ownCloud/Resources/de.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/de_CH.lproj/InfoPlist.strings b/ownCloud/Resources/de_CH.lproj/InfoPlist.strings index 69d989e7c..31069d00f 100644 Binary files a/ownCloud/Resources/de_CH.lproj/InfoPlist.strings and b/ownCloud/Resources/de_CH.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/el.lproj/InfoPlist.strings b/ownCloud/Resources/el.lproj/InfoPlist.strings index 91a17eac5..453295626 100644 Binary files a/ownCloud/Resources/el.lproj/InfoPlist.strings and b/ownCloud/Resources/el.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/en-GB.lproj/InfoPlist.strings b/ownCloud/Resources/en-GB.lproj/InfoPlist.strings index e97ffac19..4e2788526 100644 Binary files a/ownCloud/Resources/en-GB.lproj/InfoPlist.strings and b/ownCloud/Resources/en-GB.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/en-GB.lproj/Localizable.strings b/ownCloud/Resources/en-GB.lproj/Localizable.strings index f0789809d..a5657cadc 100644 Binary files a/ownCloud/Resources/en-GB.lproj/Localizable.strings and b/ownCloud/Resources/en-GB.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/en.lproj/Localizable.strings b/ownCloud/Resources/en.lproj/Localizable.strings index fdbe1486b..a8e5cbba8 100644 --- a/ownCloud/Resources/en.lproj/Localizable.strings +++ b/ownCloud/Resources/en.lproj/Localizable.strings @@ -129,6 +129,8 @@ "{{itemCount}} items with {{totalSize}} total ({{fileCount}} files, {{folderCount}} folders)" = "{{itemCount}} items with {{totalSize}} total ({{fileCount}} files, {{folderCount}} folders)"; "{{remaining}} available" = "{{remaining}} available"; +"Loading…" = "Loading…"; + "Show more results" = "Show more results"; "An error occurred" = "An error occurred"; @@ -166,6 +168,12 @@ "Welcome to %@" = "Welcome to %@"; "Please enter a server URL" = "Please enter a server URL"; "Please pick a profile to begin setup" = "Please pick a profile to begin setup"; +"The following steps will guide you through the setup process." = "The following steps will guide you through the setup process."; +"Start setup" = "Start setup"; +"Open login page" = "Open login page"; +"If you 'Continue', you will be prompted to allow the '{{app.name}}' app to open the login page where you can enter your credentials." = "If you 'Continue', you will be prompted to allow the '{{app.name}}' app to open the login page where you can enter your credentials."; +"Account setup complete" = "Account setup complete"; +"If you'd like to give the account a custom name, please enter it below:" = "If you'd like to give the account a custom name, please enter it below:"; /* Single Account */ "You are connected as\n%@" = "You are connected as\n%@"; @@ -623,6 +631,8 @@ "Add" = "Add"; "Save changes" = "Save changes"; +"Enter password" = "Enter password"; + /* Quick Access view */ "Quick Access" = "Quick Access"; "Collection" = "Collection"; @@ -860,6 +870,10 @@ "File Provider access has been disabled by the administrator. Please use the app to create new folders." = "File Provider access has been disabled by the administrator. Please use the app to create new folders."; "File Provider access has been disabled by the administrator. Please use the share extension to import files." = "File Provider access has been disabled by the administrator. Please use the share extension to import files."; +/* - No account */ +"No account has been set up in the {{app.name}} app yet." = "No account has been set up in the {{app.name}} app yet."; +"Open app" = "Open app"; + /* Disallowed Import Methods */ /* - Share Extension */ "Share Extension disabled" = "Share Extension disabled"; diff --git a/ownCloud/Resources/es.lproj/InfoPlist.strings b/ownCloud/Resources/es.lproj/InfoPlist.strings index 117bed35b..7644890d6 100644 Binary files a/ownCloud/Resources/es.lproj/InfoPlist.strings and b/ownCloud/Resources/es.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/es.lproj/Localizable.strings b/ownCloud/Resources/es.lproj/Localizable.strings index bb225df4c..e7ca61894 100644 Binary files a/ownCloud/Resources/es.lproj/Localizable.strings and b/ownCloud/Resources/es.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/et_EE.lproj/Localizable.strings b/ownCloud/Resources/et_EE.lproj/Localizable.strings index 59572d788..b9411f488 100644 Binary files a/ownCloud/Resources/et_EE.lproj/Localizable.strings and b/ownCloud/Resources/et_EE.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/fr.lproj/InfoPlist.strings b/ownCloud/Resources/fr.lproj/InfoPlist.strings index f43ade8c6..871fc4f8a 100644 Binary files a/ownCloud/Resources/fr.lproj/InfoPlist.strings and b/ownCloud/Resources/fr.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/fr.lproj/Localizable.strings b/ownCloud/Resources/fr.lproj/Localizable.strings index 7900e732d..bd1b3d7ba 100644 Binary files a/ownCloud/Resources/fr.lproj/Localizable.strings and b/ownCloud/Resources/fr.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/gl.lproj/InfoPlist.strings b/ownCloud/Resources/gl.lproj/InfoPlist.strings index f6682892d..d6b12ca14 100644 Binary files a/ownCloud/Resources/gl.lproj/InfoPlist.strings and b/ownCloud/Resources/gl.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/gl.lproj/Localizable.strings b/ownCloud/Resources/gl.lproj/Localizable.strings index 3bc439d2b..72ffa0027 100644 Binary files a/ownCloud/Resources/gl.lproj/Localizable.strings and b/ownCloud/Resources/gl.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/he.lproj/InfoPlist.strings b/ownCloud/Resources/he.lproj/InfoPlist.strings index 8a9541062..0257cff0e 100644 Binary files a/ownCloud/Resources/he.lproj/InfoPlist.strings and b/ownCloud/Resources/he.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/ko.lproj/InfoPlist.strings b/ownCloud/Resources/ko.lproj/InfoPlist.strings index 257dc025a..9a547b65e 100644 Binary files a/ownCloud/Resources/ko.lproj/InfoPlist.strings and b/ownCloud/Resources/ko.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/ko.lproj/Localizable.strings b/ownCloud/Resources/ko.lproj/Localizable.strings index 32d205481..df8c21207 100644 Binary files a/ownCloud/Resources/ko.lproj/Localizable.strings and b/ownCloud/Resources/ko.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/pt-BR.lproj/InfoPlist.strings b/ownCloud/Resources/pt-BR.lproj/InfoPlist.strings index 47b708027..9d54dfa13 100644 Binary files a/ownCloud/Resources/pt-BR.lproj/InfoPlist.strings and b/ownCloud/Resources/pt-BR.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/pt-BR.lproj/Localizable.strings b/ownCloud/Resources/pt-BR.lproj/Localizable.strings index 858b96e46..2358a1f95 100644 Binary files a/ownCloud/Resources/pt-BR.lproj/Localizable.strings and b/ownCloud/Resources/pt-BR.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/ru.lproj/InfoPlist.strings b/ownCloud/Resources/ru.lproj/InfoPlist.strings index 04d32c0d6..a021513a8 100644 Binary files a/ownCloud/Resources/ru.lproj/InfoPlist.strings and b/ownCloud/Resources/ru.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/ru.lproj/Localizable.strings b/ownCloud/Resources/ru.lproj/Localizable.strings index ed95dde9a..7771f1eae 100644 Binary files a/ownCloud/Resources/ru.lproj/Localizable.strings and b/ownCloud/Resources/ru.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/sq.lproj/InfoPlist.strings b/ownCloud/Resources/sq.lproj/InfoPlist.strings index 13c3d54d3..f7508c4e3 100644 Binary files a/ownCloud/Resources/sq.lproj/InfoPlist.strings and b/ownCloud/Resources/sq.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/sq.lproj/Localizable.strings b/ownCloud/Resources/sq.lproj/Localizable.strings index cb3fcc68a..dfacce0e9 100644 Binary files a/ownCloud/Resources/sq.lproj/Localizable.strings and b/ownCloud/Resources/sq.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/th-TH.lproj/InfoPlist.strings b/ownCloud/Resources/th-TH.lproj/InfoPlist.strings index bf930021f..9f50e3a4a 100644 Binary files a/ownCloud/Resources/th-TH.lproj/InfoPlist.strings and b/ownCloud/Resources/th-TH.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/th-TH.lproj/Localizable.strings b/ownCloud/Resources/th-TH.lproj/Localizable.strings index e8fafc7f4..24450ee70 100644 Binary files a/ownCloud/Resources/th-TH.lproj/Localizable.strings and b/ownCloud/Resources/th-TH.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/tr.lproj/InfoPlist.strings b/ownCloud/Resources/tr.lproj/InfoPlist.strings index 00de75866..bc8b585ab 100644 Binary files a/ownCloud/Resources/tr.lproj/InfoPlist.strings and b/ownCloud/Resources/tr.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/tr.lproj/Localizable.strings b/ownCloud/Resources/tr.lproj/Localizable.strings index 825884800..9b8223e8b 100644 Binary files a/ownCloud/Resources/tr.lproj/Localizable.strings and b/ownCloud/Resources/tr.lproj/Localizable.strings differ diff --git a/ownCloud/Resources/zh-Hans.lproj/InfoPlist.strings b/ownCloud/Resources/zh-Hans.lproj/InfoPlist.strings index 0efdfb133..76e7fe67b 100644 Binary files a/ownCloud/Resources/zh-Hans.lproj/InfoPlist.strings and b/ownCloud/Resources/zh-Hans.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/zh_TW.lproj/InfoPlist.strings b/ownCloud/Resources/zh_TW.lproj/InfoPlist.strings index f10856a05..c919b9641 100644 Binary files a/ownCloud/Resources/zh_TW.lproj/InfoPlist.strings and b/ownCloud/Resources/zh_TW.lproj/InfoPlist.strings differ diff --git a/ownCloud/Resources/zh_TW.lproj/Localizable.strings b/ownCloud/Resources/zh_TW.lproj/Localizable.strings index 0294e7653..49a347ef8 100644 Binary files a/ownCloud/Resources/zh_TW.lproj/Localizable.strings and b/ownCloud/Resources/zh_TW.lproj/Localizable.strings differ diff --git a/ownCloud/SceneDelegate.swift b/ownCloud/SceneDelegate.swift index 51487ec75..408875bc0 100644 --- a/ownCloud/SceneDelegate.swift +++ b/ownCloud/SceneDelegate.swift @@ -51,7 +51,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = ThemeWindow(windowScene: windowScene) window?.rootViewController = appRootViewController - window?.addSubview(appRootViewController.view) + if #available(iOS 16, *) { + // From the console: "Manually adding the rootViewController's view to the view hierarchy is no longer supported. Please allow UIWindow to add the rootViewController's view to the view hierarchy itself." + } else { + window?.addSubview(appRootViewController.view) + } window?.makeKeyAndVisible() } diff --git a/ownCloud/Server List/ServerListBookmarkCell.swift b/ownCloud/Server List/ServerListBookmarkCell.swift deleted file mode 100644 index 89dbefed1..000000000 --- a/ownCloud/Server List/ServerListBookmarkCell.swift +++ /dev/null @@ -1,190 +0,0 @@ -// -// ServerListBookmarkCell.swift -// ownCloud -// -// Created by Felix Schwarz on 08.03.18. -// Copyright © 2018 ownCloud GmbH. All rights reserved. -// - -/* - * Copyright (C) 2018, ownCloud GmbH. - * - * This code is covered by the GNU Public License Version 3. - * - * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ - * You should have received a copy of this license along with this program. If not, see . - * - */ - -import UIKit -import ownCloudSDK -import ownCloudApp -import ownCloudAppShared - -class ServerListBookmarkCell : ThemeTableViewCell { - static private let iconSideLength : CGFloat = 40 - - public var titleLabel : UILabel = UILabel() - public var detailLabel : UILabel = UILabel() - public var logoFallbackView : UIImageView = UIImageView() - public var iconView : ResourceViewHost = ResourceViewHost(fallbackSize: CGSize(width: ServerListBookmarkCell.iconSideLength, height: ServerListBookmarkCell.iconSideLength)) - public var infoView : UIView = UIView() - - public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - prepareViewAndConstraints() - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - func prepareViewAndConstraints() { - self.selectionStyle = .default - - PointerEffect.install(on: self.contentView, effectStyle: .hover) - - titleLabel.translatesAutoresizingMaskIntoConstraints = false - detailLabel.translatesAutoresizingMaskIntoConstraints = false - iconView.translatesAutoresizingMaskIntoConstraints = false - logoFallbackView.translatesAutoresizingMaskIntoConstraints = false - infoView.translatesAutoresizingMaskIntoConstraints = false - - logoFallbackView.contentMode = .scaleAspectFit - logoFallbackView.image = Branding.shared.brandedImageNamed(.bookmarkIcon) - - iconView.fallbackView = logoFallbackView - - titleLabel.font = UIFont.preferredFont(forTextStyle: .headline) - titleLabel.adjustsFontForContentSizeCategory = true - - detailLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) - detailLabel.adjustsFontForContentSizeCategory = true - - detailLabel.textColor = UIColor.gray - - contentView.addSubview(titleLabel) - contentView.addSubview(detailLabel) - contentView.addSubview(iconView) - contentView.addSubview(infoView) - - NSLayoutConstraint.activate([ - iconView.widthAnchor.constraint(equalToConstant: ServerListBookmarkCell.iconSideLength), - iconView.heightAnchor.constraint(equalToConstant: ServerListBookmarkCell.iconSideLength), - iconView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - - iconView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), - iconView.trailingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: -25), - iconView.trailingAnchor.constraint(equalTo: detailLabel.leadingAnchor, constant: -25), - - titleLabel.trailingAnchor.constraint(equalTo: infoView.leadingAnchor), - titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20), - titleLabel.bottomAnchor.constraint(equalTo: detailLabel.topAnchor, constant: -5), - - detailLabel.trailingAnchor.constraint(equalTo: infoView.leadingAnchor), - detailLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20), - - infoView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20), - infoView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20), - infoView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20) - ]) - - infoView.setContentHuggingPriority(.required, for: .horizontal) - logoFallbackView.setContentHuggingPriority(.required, for: .vertical) - titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) - detailLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) - - Theme.shared.add(tvgResourceFor: "owncloud-logo") - - NotificationCenter.default.addObserver(self, selector: #selector(ServerListBookmarkCell.updateMessageBadgeFrom(notification:)), name: .BookmarkMessageCountChanged, object: nil) - } - - deinit { - NotificationCenter.default.removeObserver(self, name: .BookmarkMessageCountChanged, object: nil) - } - - // MARK: - Content updates - var messageSelector : MessageSelector? - var directMessageCountTrackingEnabled : Bool = false - var bookmark : OCBookmark? { - didSet { - if let bookmark = bookmark { - titleLabel.text = bookmark.shortName - detailLabel.text = bookmark.url?.absoluteString - accessibilityIdentifier = "server-bookmark-cell" - - iconView.activeViewProvider = bookmark.avatar - - if directMessageCountTrackingEnabled { - messageSelector = MessageSelector(from: .global, filter: { (message) -> Bool in - return (message.bookmarkUUID == bookmark.uuid) && !message.resolved - }, handler: { [weak self] (messages, _, _) in - OnMainThread { - self?.updateMessageBadge(count: (messages != nil) ? messages!.count : 0) - } - }) - } - } else { - if directMessageCountTrackingEnabled { - messageSelector = nil - } - } - } - } - - // MARK: - Message Badge - private var badgeLabel : RoundedLabel? - - func updateMessageBadge(count: Int) { - if count > 0 { - if badgeLabel == nil { - badgeLabel = RoundedLabel(text: "", style: .token) - badgeLabel?.translatesAutoresizingMaskIntoConstraints = false - - if let badgeLabel = badgeLabel { - infoView.addSubview(badgeLabel) - - NSLayoutConstraint.activate([ - badgeLabel.leadingAnchor.constraint(equalTo: infoView.leadingAnchor, constant: 20), - badgeLabel.trailingAnchor.constraint(equalTo: infoView.trailingAnchor), - badgeLabel.centerYAnchor.constraint(equalTo: infoView.centerYAnchor) - ]) - } - } - - badgeLabel?.labelText = "\(count)" - } else { - badgeLabel?.removeFromSuperview() - badgeLabel = nil - } - } - - @objc func updateMessageBadgeFrom(notification: Notification) { - if let countByBookmarkUUID = notification.object as? ServerListTableViewController.ServerListTableMessageCountByUUID, let bookmarkUUID = bookmark?.uuid { - self.updateMessageBadge(count: countByBookmarkUUID[bookmarkUUID] ?? 0) - } - } - - // MARK: - Themeing - override func applyThemeCollectionToCellContents(theme: Theme, collection: ThemeCollection) { - let itemState = ThemeItemState(selected: self.isSelected) - - self.titleLabel.applyThemeCollection(collection, itemStyle: .title, itemState: itemState) - self.detailLabel.applyThemeCollection(collection, itemStyle: .message, itemState: itemState) - if !VendorServices.shared.isBranded { - self.logoFallbackView.image = self.logoFallbackView.image?.tinted(with: collection.tableRowColors.labelColor) - } - } - - override func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { - let itemState = ThemeItemState(selected: self.isSelected) - - super.applyThemeCollection(theme: theme, collection: collection, event: event) - - self.titleLabel.applyThemeCollection(collection, itemStyle: .title, itemState: itemState) - self.detailLabel.applyThemeCollection(collection, itemStyle: .message, itemState: itemState) - if !VendorServices.shared.isBranded { - self.logoFallbackView.image = self.logoFallbackView.image?.tinted(with: collection.tableRowColors.labelColor) - } - } -} diff --git a/ownCloud/Settings/UserInterfaceSettingsSection.swift b/ownCloud/Settings/UserInterfaceSettingsSection.swift index 06dd04ab4..a45696611 100644 --- a/ownCloud/Settings/UserInterfaceSettingsSection.swift +++ b/ownCloud/Settings/UserInterfaceSettingsSection.swift @@ -18,6 +18,7 @@ import UIKit import ownCloudSDK +import ownCloudApp import ownCloudAppShared class UserInterfaceSettingsSection: SettingsSection { @@ -35,7 +36,7 @@ class UserInterfaceSettingsSection: SettingsSection { self?.pushThemeStyleSelector() }, title: "Theme".localized, value: ThemeStyle.displayName, accessoryType: .disclosureIndicator, identifier: "theme") - if !VendorServices.shared.isBranded { + if Branding.shared.allowThemeSelection { self.add(row: themeRow!) } @@ -83,9 +84,9 @@ class UserInterfaceSettingsSection: SettingsSection { ThemeStyle.followSystemAppearance = true themeRow?.cell?.detailTextLabel?.text = "System".localized } else if let styleIdentifier = row.value as? String, - let style = ThemeStyle.forIdentifier(styleIdentifier), ThemeStyle.preferredStyle != style { - ThemeStyle.followSystemAppearance = false + let style = ThemeStyle.forIdentifier(styleIdentifier) { ThemeStyle.preferredStyle = style + ThemeStyle.followSystemAppearance = false themeRow?.cell?.detailTextLabel?.text = ThemeStyle.displayName } diff --git a/ownCloud/UI Elements/ImageScrollView.swift b/ownCloud/UI Elements/ImageScrollView.swift index 88b9707fa..999a6cbef 100644 --- a/ownCloud/UI Elements/ImageScrollView.swift +++ b/ownCloud/UI Elements/ImageScrollView.swift @@ -17,14 +17,17 @@ */ import UIKit +import VisionKit +import ownCloudSDK +import ownCloudAppShared class ImageScrollView: UIScrollView { - // MARK: - Constants private let MAXIMUM_ZOOM_SCALE: CGFloat = 6.0 // MARK: - Instance Variables - private var imageView: UIImageView! + private var imageView: UIImageView? + private var imageAnalysisInteraction: Any? // MARK: - Init override init(frame: CGRect) { @@ -49,12 +52,12 @@ class ImageScrollView: UIScrollView { // MARK: - Manage Scale private func centerImage() { - guard imageView != nil else { + guard let imageView else { return } let boundsSize: CGSize = bounds.size - var frameToCenter: CGRect = imageView?.frame ?? .zero + var frameToCenter: CGRect = imageView.frame // center horizontally if frameToCenter.size.width < boundsSize.width { @@ -94,7 +97,6 @@ class ImageScrollView: UIScrollView { // MARK: - Public API extension ImageScrollView { - func updateScaleForRotation(size: CGSize) { contentSize = size setMinZoomScaleForCurrentBounds(size) @@ -106,11 +108,58 @@ extension ImageScrollView { func display(image: UIImage, inSize: CGSize) { imageView?.removeFromSuperview() + imageView = UIImageView(image: image) + guard let imageView else { return } + imageView.accessibilityIdentifier = "loaded-image-gallery" imageView.contentMode = .scaleAspectFit + addSubview(imageView) updateScaleForRotation(size: inSize) + + if imageInteractionsAllowed { + analyzeImage(image: image) + } + } + + var hasActiveImageAnalysisSelection: Bool { + if #available(iOS 16, *) { + if let interaction = imageAnalysisInteraction as? ImageAnalysisInteraction { + return interaction.selectableItemsHighlighted + } + } + + return false + } + + func analyzeImage(image: UIImage) { + if #available(iOS 16, *) { + guard ImageAnalyzer.isSupported else { + return + } + + let interaction = ImageAnalysisInteraction() + imageView?.addInteraction(interaction) + + imageAnalysisInteraction = interaction + + Task.detached(priority: .userInitiated, operation: { + do { + let configuration = ImageAnalyzer.Configuration([.machineReadableCode, .text, .visualLookUp]) + let analyzer = ImageAnalyzer() + let analysis = try await analyzer.analyze(image, configuration: configuration) + await MainActor.run { + interaction.analysis = analysis + interaction.preferredInteractionTypes = [.automatic] + } + } catch { + await MainActor.run(body: { + interaction.preferredInteractionTypes = [] + }) + } + }) + } } } @@ -125,3 +174,28 @@ extension ImageScrollView: UIScrollViewDelegate { } } + +// MARK: - Class Settings +public extension OCClassSettingsKey { + static let allowImageInteractions = OCClassSettingsKey("allow-image-interactions") +} + +extension ImageScrollView { + static func registerImageInteractionsSettings() { + Action.registerOCClassSettingsDefaults([ + .allowImageInteractions : true + ], metadata: [ + .allowImageInteractions : [ + .type : OCClassSettingsMetadataType.boolean, + .label : "Allow Image Interactions", + .description : "Allow (true) or disallow (false) text/selection/OCR interactions with images.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Actions" + ] + ]) + } + + var imageInteractionsAllowed: Bool { + return Action.classSetting(forOCClassSettingsKey: .allowImageInteractions) as? Bool ?? true + } +} diff --git a/ownCloudAppFramework/Branding/Branding.h b/ownCloudAppFramework/Branding/Branding.h index 8c7a00faa..a13114906 100644 --- a/ownCloudAppFramework/Branding/Branding.h +++ b/ownCloudAppFramework/Branding/Branding.h @@ -26,6 +26,7 @@ typedef NSString* BrandingLegacyKey; typedef OCClassSettingsKey BrandingKey NS_TYPED_EXTENSIBLE_ENUM; typedef NSString* BrandingFileImportMethod NS_TYPED_EXTENSIBLE_ENUM; typedef NSString* BrandingImageName NS_TYPED_EXTENSIBLE_ENUM; +typedef NSString* BrandingAssetSuffix NS_TYPED_EXTENSIBLE_ENUM; @protocol BrandingInitialization + (void)initializeBranding; @@ -41,6 +42,7 @@ typedef NSString* BrandingImageName NS_TYPED_EXTENSIBLE_ENUM; @property(strong,nonatomic,readonly,class) Branding *sharedBranding; @property(assign,nonatomic) BOOL allowBranding; //!< YES if branding is allowed. If NO, computedValueForClassSettingsKey returns only default values. +@property(assign,nonatomic) BOOL allowThemeSelection; //!< YES if theme selection is allowed @property(strong,nullable,nonatomic,readonly) NSBundle *appBundle; //!< Bundle of the main app @@ -60,6 +62,7 @@ typedef NSString* BrandingImageName NS_TYPED_EXTENSIBLE_ENUM; - (BOOL)isImportMethodAllowed:(BrandingFileImportMethod)importMethod; - (nullable UIImage *)brandedImageNamed:(BrandingImageName)imageName; //!< Returns the respective image from the appBundle +- (nullable UIImage *)brandedImageNamed:(BrandingImageName)imageName assetSuffix:(nullable BrandingAssetSuffix)assetSuffix; //!< Returns the respective image from the appBundle, trying to retrieve a more specific asset with the provided suffix (if provided) - (nullable id)computedValueForClassSettingsKey:(OCClassSettingsKey)classSettingsKey; - (nullable NSURL *)urlForClassSettingsKey:(OCClassSettingsKey)settingsKey; diff --git a/ownCloudAppFramework/Branding/Branding.m b/ownCloudAppFramework/Branding/Branding.m index 58f0bd5b7..aa9c60f01 100644 --- a/ownCloudAppFramework/Branding/Branding.m +++ b/ownCloudAppFramework/Branding/Branding.m @@ -75,6 +75,7 @@ - (instancetype)init _brandingPlistURL = [appBundle URLForResource:@"Branding" withExtension:@"plist"]; _allowBranding = YES; + _allowThemeSelection = YES; NSData *brandingPlistData; @@ -212,6 +213,21 @@ - (nullable UIImage *)brandedImageNamed:(BrandingImageName)imageName return ([UIImage imageNamed:imageName inBundle:self.appBundle compatibleWithTraitCollection:nil]); } +- (nullable UIImage *)brandedImageNamed:(BrandingImageName)imageName assetSuffix:(BrandingAssetSuffix)assetSuffix +{ + UIImage *image = nil; + + if (assetSuffix != nil) { + image = [UIImage imageNamed:[imageName stringByAppendingFormat:@"-%@", assetSuffix] inBundle:self.appBundle compatibleWithTraitCollection:nil]; + } + + if (image == nil) { + image = [UIImage imageNamed:imageName inBundle:self.appBundle compatibleWithTraitCollection:nil]; + } + + return (image); +} + - (nullable id)computedValueForClassSettingsKey:(OCClassSettingsKey)classSettingsKey { id value = nil; diff --git a/ownCloudAppFramework/Building/BuildOptions.m b/ownCloudAppFramework/Building/BuildOptions.m index 36219f607..edc5fa5a6 100644 --- a/ownCloudAppFramework/Building/BuildOptions.m +++ b/ownCloudAppFramework/Building/BuildOptions.m @@ -21,6 +21,7 @@ OCClassSettingsIdentifier OCClassSettingsIdentifierBuildOptions = @"build"; OCClassSettingsKey OCClassSettingsKeyBuildFlags = @"flags"; +OCClassSettingsKey OCClassSettingsKeyVersionNumber = @"version-number"; OCClassSettingsKey OCClassSettingsKeyCustomAppScheme = @"custom-app-scheme"; OCClassSettingsKey OCClassSettingsKeyCustomAuthScheme = @"custom-auth-scheme"; OCClassSettingsKey OCClassSettingsKeyAppGroupIdentifier = @"app-group-identifier"; @@ -58,6 +59,14 @@ + (OCClassSettingsMetadataCollection)classSettingsMetadata OCClassSettingsMetadataKeyCategory : @"Build", }, + // build.version-number + OCClassSettingsKeyVersionNumber : @{ + OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeString, + OCClassSettingsMetadataKeyDescription : @"Sets a custom version number for the app.", + OCClassSettingsMetadataKeyStatus : OCClassSettingsKeyStatusSupported, + OCClassSettingsMetadataKeyCategory : @"Build", + }, + // build.custom-app-scheme OCClassSettingsKeyCustomAppScheme : @{ OCClassSettingsMetadataKeyType : OCClassSettingsMetadataTypeString, diff --git a/ownCloudAppShared/Branding/BrandView.swift b/ownCloudAppShared/Branding/BrandView.swift new file mode 100644 index 000000000..7699b71d9 --- /dev/null +++ b/ownCloudAppShared/Branding/BrandView.swift @@ -0,0 +1,166 @@ +// +// BrandView.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 12.10.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudApp + +open class BrandView: UIView { + open var showBackground: Bool = true { + didSet { + render() + } + } + + open var showLogo: Bool = true { + didSet { + render() + } + } + open var logoMaxSize: CGSize? { + didSet { + render() + } + } + open var fitToLogo: Bool = false { + didSet { + render() + } + } + + open var roundedCorners: Bool = true { + didSet { + render() + } + } + open var assetSuffix: BrandingAssetSuffix? { + didSet { + render() + } + } + + public init(showBackground: Bool, showLogo: Bool, logoMaxSize: CGSize? = nil, fitToLogo: Bool = false, roundedCorners: Bool, assetSuffix: BrandingAssetSuffix? = nil) { + super.init(frame: CGRect(x: 0, y: 0, width: 128, height: 128)) + + translatesAutoresizingMaskIntoConstraints = false + + self.showBackground = showBackground + + self.showLogo = showLogo + self.logoMaxSize = logoMaxSize + self.fitToLogo = fitToLogo + + self.roundedCorners = roundedCorners + self.assetSuffix = assetSuffix + + self.cssSelector = .brand + + render() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Views + private var backgroundColorView: ThemeCSSView? + private var backgroundImageView: UIImageView? + private var logoImageView: UIImageView? + + func render() { + // Create and add background views + backgroundColorView?.removeFromSuperview() + backgroundColorView = nil + + backgroundImageView?.removeFromSuperview() + backgroundImageView = nil + + if showBackground { + // Add background color + backgroundColorView = ThemeCSSView(withSelectors: [.background]) + backgroundColorView?.translatesAutoresizingMaskIntoConstraints = false + embed(toFillWith: backgroundColorView!) + + // Add background image + if let backgroundImage = Branding.shared.brandedImageNamed(.brandBackground, assetSuffix: assetSuffix) ?? Branding.shared.brandedImageNamed(.legacyBrandBackground, assetSuffix: assetSuffix) { + backgroundImageView = UIImageView(image: backgroundImage) + backgroundImageView?.translatesAutoresizingMaskIntoConstraints = false + backgroundImageView?.contentMode = .scaleAspectFill + backgroundImageView?.cssSelector = .background + backgroundImageView?.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 700), for: .horizontal) // make background image view have a smaller compression resistance than the parent view (defaults to 750), in order not to establish that the background image width does not determine the superview's width + + embed(toFillWith: backgroundImageView!) + } + + // Apply rounded corners + if roundedCorners { + let cornerRadius: CGFloat = 10 + + backgroundColorView?.layer.cornerRadius = cornerRadius + backgroundColorView?.clipsToBounds = true + + backgroundImageView?.layer.cornerRadius = cornerRadius + backgroundImageView?.clipsToBounds = true + + layer.cornerRadius = cornerRadius + clipsToBounds = true + } + } + + // Logo image view + logoImageView?.removeFromSuperview() + logoImageView = nil + + if showLogo { + if let logoImage = Branding.shared.brandedImageNamed(.brandLogo, assetSuffix: assetSuffix) ?? Branding.shared.brandedImageNamed(.legacyBrandLogo, assetSuffix: assetSuffix) { + logoImageView = UIImageView(image: logoImage) + logoImageView?.translatesAutoresizingMaskIntoConstraints = false + logoImageView?.contentMode = .scaleAspectFit + logoImageView?.accessibilityLabel = VendorServices.shared.appName + logoImageView?.cssSelector = .icon + + if let logoImageView { + // Apply aspect ratio maximum size + if let logoMaxSize { + let logoImageSize = UIImage.sizeThatFits(logoImage.size, into: logoMaxSize) + + NSLayoutConstraint.activate([ + logoImageView.widthAnchor.constraint(equalToConstant: logoImageSize.width), + logoImageView.heightAnchor.constraint(equalToConstant: logoImageSize.height) + ]) + } + + if fitToLogo { + embed(toFillWith: logoImageView) + } else { + addSubview(logoImageView) + NSLayoutConstraint.activate([ + logoImageView.centerXAnchor.constraint(equalTo: centerXAnchor), + logoImageView.centerYAnchor.constraint(equalTo: centerYAnchor) + ]) + } + } + } + } + } +} + +extension BrandView: DataItemSelectionInteraction { + public func allowSelection(in viewController: UIViewController?, section: CollectionViewSection?, with context: ClientContext?) -> Bool { + return false + } +} diff --git a/ownCloudAppShared/Branding/Branding+App.swift b/ownCloudAppShared/Branding/Branding+App.swift index 4f3668a6d..ebf46832b 100644 --- a/ownCloudAppShared/Branding/Branding+App.swift +++ b/ownCloudAppShared/Branding/Branding+App.swift @@ -51,8 +51,18 @@ extension OCClassSettingsKey { public static let profileDefinitions : OCClassSettingsKey = OCClassSettingsKey("profile-definitions") // Themes - public static let themeGenericColors : OCClassSettingsKey = OCClassSettingsKey("theme-generic-colors") - public static let themeDefinitions : OCClassSettingsKey = OCClassSettingsKey("theme-definitions") + public static let themeDefinitions: OCClassSettingsKey = OCClassSettingsKey("theme-definitions") + + public static let themeColors: OCClassSettingsKey = OCClassSettingsKey("theme-colors") + public static let themeCSSRecords: OCClassSettingsKey = OCClassSettingsKey("theme-css-records") +} + +enum BrandingColorAlias: String, CaseIterable { + case tintColor = "tint-color" + case brandingBackgroundColor = "branding-background-color" + case setupStatusBarStyle = "setup-status-bar-style" + case folderIconColor = "folder-icon-color" + case fileIconColor = "file-icon-color" } extension Branding : BrandingInitialization { @@ -65,7 +75,8 @@ extension Branding : BrandingInitialization { .sendFeedbackAddress : "ios-app@owncloud.com", .canAddAccount : true, .canEditAccount : true, - .enableReviewPrompt : false + .enableReviewPrompt : false, + .profileAllowUrlConfiguration : true ], metadata: [ .documentationURL : [ .type : OCClassSettingsMetadataType.urlString, @@ -154,82 +165,113 @@ extension Branding : BrandingInitialization { .status : OCClassSettingsKeyStatus.advanced ], - .themeGenericColors : [ - .type : OCClassSettingsMetadataType.dictionary, - .description : "Dictionary defining generic colors that can be used in the definitions.", + .themeDefinitions : [ + .type : OCClassSettingsMetadataType.dictionaryArray, + .description : "Array of dictionaries, each specifying a theme.", .category : "Branding", .status : OCClassSettingsKeyStatus.advanced ], - .themeDefinitions : [ - .type : OCClassSettingsMetadataType.dictionaryArray, - .description : "Array of dictionaries, each specifying a theme.", + .themeCSSRecords: [ + .type : OCClassSettingsMetadataType.stringArray, + .label : "Theme CSS Records", + .description : "CSS records to add to the CSS space of system-color-based themes for branded clients. Mutually exclusive with theme-definitions.", + .category : "Branding", + .status : OCClassSettingsKeyStatus.advanced + ], + + .themeColors : [ + .type : OCClassSettingsMetadataType.dictionary, + .label : "Theme Colors", + .description : "Values to use in system-color-based themes for branded clients. Mutually exclusive with theme-definitions.", .category : "Branding", + .possibleKeys : [ + [ + OCClassSettingsMetadataKey.value : BrandingColorAlias.tintColor.rawValue, + OCClassSettingsMetadataKey.description : "Color to use as tint/accent color for controls (in hex notation)." + ], + [ + OCClassSettingsMetadataKey.value : BrandingColorAlias.brandingBackgroundColor.rawValue, + OCClassSettingsMetadataKey.description : "Color to use as background color for brand views (in hex notation)." + ], + [ + OCClassSettingsMetadataKey.value : BrandingColorAlias.setupStatusBarStyle.rawValue, + OCClassSettingsMetadataKey.description : "The status bar style in the setup wizard, affecting the status bar text color. Can be either `default`, `black` or `white`." + ], + [ + OCClassSettingsMetadataKey.value : BrandingColorAlias.fileIconColor.rawValue, + OCClassSettingsMetadataKey.description : "Color to fill file icons with (in hex notation)." + ], + [ + OCClassSettingsMetadataKey.value : BrandingColorAlias.folderIconColor.rawValue, + OCClassSettingsMetadataKey.description : "Color to fill folder icons with (in hex notation)." + ] + ], .status : OCClassSettingsKeyStatus.advanced ], .profileBookmarkName : [ - .type : OCClassSettingsMetadataType.string, - .label : "Bookmark Name", - .description : "The name that should be used for the bookmark that's generated from this profile and appears in the account list.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" + .type : OCClassSettingsMetadataType.string, + .label : "Bookmark Name", + .description : "The name that should be used for the bookmark that's generated from this profile and appears in the account list.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" ], .profileURL : [ - .type : OCClassSettingsMetadataType.urlString, - .label : "URL", - .description : "The URL of the server targeted by this profile.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" + .type : OCClassSettingsMetadataType.urlString, + .label : "URL", + .description : "The URL of the server targeted by this profile.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" ], .profileHelpURL : [ - .type : OCClassSettingsMetadataType.urlString, - .label : "Onboarding URL", - .description : "Optional URL to onboarding resources.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" + .type : OCClassSettingsMetadataType.urlString, + .label : "Onboarding URL", + .description : "Optional URL to onboarding resources.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" ], .profileOpenHelpMessage: [ - .type : OCClassSettingsMetadataType.string, - .label : "Open onboarding URL message", - .description : "Message shown in an alert before opening the onboarding URL.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" + .type : OCClassSettingsMetadataType.string, + .label : "Open onboarding URL message", + .description : "Message shown in an alert before opening the onboarding URL.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" ], .profileHelpButtonLabel : [ - .type : OCClassSettingsMetadataType.string, - .label : "Onboarding button title", - .description : "Text used for the onboarding button title", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" + .type : OCClassSettingsMetadataType.string, + .label : "Onboarding button title", + .description : "Text used for the onboarding button title", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" ], .profileAllowUrlConfiguration : [ - .type : OCClassSettingsMetadataType.boolean, - .label : "Allow URL configuration", - .description : "Indicates if the user can change the server URL for the account.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" + .type : OCClassSettingsMetadataType.boolean, + .label : "Allow URL configuration", + .description : "Indicates if the user can change the server URL for the account.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" ], .sidebarLinks : [ - .type : OCClassSettingsMetadataType.array, - .label : "Sidebar Link Items", - .description : "Array of Dictionary, which should appear in the sidebar. Keys url and title are mandatory and an optional image can be added as either an SF-Symbol name (key: symbol) or the name of an image bundled with the app (key: image)", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" + .type : OCClassSettingsMetadataType.array, + .label : "Sidebar Link Items", + .description : "Array of Dictionary, which should appear in the sidebar. Keys url and title are mandatory and an optional image can be added as either an SF-Symbol name (key: symbol) or the name of an image bundled with the app (key: image)", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" ], .sidebarLinksTitle : [ - .type : OCClassSettingsMetadataType.string, - .label : "Sidebar Links Title", - .description : "Title for the sidebar links section.", - .status : OCClassSettingsKeyStatus.advanced, - .category : "Branding" + .type : OCClassSettingsMetadataType.string, + .label : "Sidebar Links Title", + .description : "Title for the sidebar links section.", + .status : OCClassSettingsKeyStatus.advanced, + .category : "Branding" ] ]) } @@ -249,20 +291,25 @@ extension Branding : BrandingInitialization { registerLegacyKeyPath("Profiles", forClassSettingsKey: .profileDefinitions) - registerLegacyKeyPath("Generic", forClassSettingsKey: .themeGenericColors) registerLegacyKeyPath("Themes", forClassSettingsKey: .themeDefinitions) // swiftlint:enable comma } } extension BrandingImageName { - public static let loginLogo : BrandingImageName = BrandingImageName("branding-login-logo") - public static let loginBackground : BrandingImageName = BrandingImageName("branding-login-background") + public static let brandLogo : BrandingImageName = BrandingImageName("branding-logo") + public static let brandBackground : BrandingImageName = BrandingImageName("branding-background") - public static let splashscreenLogo : BrandingImageName = BrandingImageName("branding-splashscreen") + public static let legacyBrandLogo : BrandingImageName = BrandingImageName("branding-login-logo") // can be removed as of version 12.2 + public static let legacyBrandBackground : BrandingImageName = BrandingImageName("branding-login-background") // can be removed as of version 12.2 + + public static let splashscreenLogo : BrandingImageName = BrandingImageName("branding-splashscreen-logo") public static let splashscreenBackground : BrandingImageName = BrandingImageName("branding-splashscreen-background") +} - public static let bookmarkIcon : BrandingImageName = BrandingImageName("branding-bookmark-icon") +extension BrandingAssetSuffix { + public static let setup: BrandingAssetSuffix = BrandingAssetSuffix("setup") + public static let sidebar: BrandingAssetSuffix = BrandingAssetSuffix("sidebar") } extension Branding { @@ -399,7 +446,7 @@ public struct SidebarLink { } extension Branding { - func generateThemeStyle(from theme: [String : Any], generic: [String : Any]) -> ThemeStyle? { + func generateThemeStyle(from theme: [String : Any]) -> ThemeStyle? { let style = theme["ThemeStyle"] as? String ?? ThemeCollectionStyle.light.rawValue let identifier = theme["Identifier"] as? String ?? "com.owncloud.branding" let name = theme["Name"] as? String ?? "ownCloud-branding-theme" @@ -408,29 +455,78 @@ extension Branding { if let themeStyle = ThemeCollectionStyle(rawValue: style), let darkBrandColor = theme["darkBrandColor"] as? String, let lightBrandColor = theme["lightBrandColor"] as? String { - let colors = theme["Colors"] as? NSDictionary let styles = theme["Styles"] as? NSDictionary - return ThemeStyle(styleIdentifier: identifier, localizedName: name.localized, lightColor: lightBrandColor.colorFromHex ?? UIColor.red, darkColor: darkBrandColor.colorFromHex ?? UIColor.blue, themeStyle: themeStyle, customColors: colors, genericColors: generic as NSDictionary?, interfaceStyles: styles, cssRecordStrings: cssRecordStrings) + return ThemeStyle(styleIdentifier: identifier, localizedName: name.localized, lightColor: lightBrandColor.colorFromHex ?? UIColor.red, darkColor: darkBrandColor.colorFromHex ?? UIColor.blue, themeStyle: themeStyle, interfaceStyles: styles, cssRecordStrings: cssRecordStrings) } return nil } public func setupThemeStyles() -> Bool { - var extractedThemeStyles : [ThemeStyle] = [] + var brandingThemeStyles : [ThemeStyle] = [] + + allowThemeSelection = true if let themeStyleDefinitions = self.computedValue(forClassSettingsKey: .themeDefinitions) as? [[String : Any]] { - let generic = self.computedValue(forClassSettingsKey: .themeGenericColors) as? [String : Any] ?? [:] for themeStyleDefinition in themeStyleDefinitions { - if let themeStyle = self.generateThemeStyle(from: themeStyleDefinition, generic: generic) { - extractedThemeStyles.append(themeStyle) + if let themeStyle = self.generateThemeStyle(from: themeStyleDefinition) { + brandingThemeStyles.append(themeStyle) } } + } else if isBranded { + var tintColor: UIColor? + var mappedCSSRecordStrings : [String]? + + if let colors = self.computedValue(forClassSettingsKey: .themeColors) as? [String:String] { + func addCSSRecord(_ selectors: [ThemeCSSSelector], property: ThemeCSSProperty, value: String) { + if mappedCSSRecordStrings == nil { + mappedCSSRecordStrings = [] + } + + mappedCSSRecordStrings?.append("\(selectors.address(with: property)): \(value)") + } + + for (aliasString, value) in colors { + if let alias = BrandingColorAlias(rawValue: aliasString) { + switch alias { + case .tintColor: + // Use as tint color + tintColor = value.colorFromHex + + case .brandingBackgroundColor: + addCSSRecord([.brand, .background], property: .fill, value: value) + + case .setupStatusBarStyle: + addCSSRecord([.accountSetup], property: .statusBarStyle, value: value) + + case .folderIconColor: + addCSSRecord([.vectorImage, .folderColor], property: .fill, value: value) + + case .fileIconColor: + addCSSRecord([.vectorImage, .fileColor], property: .fill, value: value) + addCSSRecord([.vectorImage, .documentFileColor], property: .fill, value: value) + addCSSRecord([.vectorImage, .presentationFileColor], property: .fill, value: value) + addCSSRecord([.vectorImage, .spreadsheetFileColor], property: .fill, value: value) + addCSSRecord([.vectorImage, .pdfFileColor], property: .fill, value: value) + } + } + } + } + + var effectiveCSSRecordStrings: [String] = mappedCSSRecordStrings ?? [] + + if let themeCSSRecordStrings = self.computedValue(forClassSettingsKey: .themeCSSRecords) as? [String] { + effectiveCSSRecordStrings.append(contentsOf: themeCSSRecordStrings) + } + + brandingThemeStyles.append(ThemeStyle.systemLight(with: tintColor, cssRecordStrings: effectiveCSSRecordStrings)) + brandingThemeStyles.append(ThemeStyle.systemDark(with: tintColor, cssRecordStrings: effectiveCSSRecordStrings)) + allowThemeSelection = true } var isDefault = true - for themeStyle in extractedThemeStyles { + for themeStyle in brandingThemeStyles { let themeStyleExtension = themeStyle.themeStyleExtension(isDefault: isDefault) OCExtensionManager.shared.addExtension(themeStyleExtension) isDefault = false @@ -442,4 +538,9 @@ extension Branding { public extension ThemeCSSSelector { static let welcome = ThemeCSSSelector(rawValue: "welcome") + static let accountSetup = ThemeCSSSelector(rawValue: "accountSetup") + static let step = ThemeCSSSelector(rawValue: "step") + static let help = ThemeCSSSelector(rawValue: "help") + static let certificateSummary = ThemeCSSSelector(rawValue: "certificateSummary") + static let brand = ThemeCSSSelector(rawValue: "brand") } diff --git a/ownCloudAppShared/Client/Collection Views/Cells/AccountControllerCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/AccountControllerCell.swift index 9e2e6a92e..25a061f22 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/AccountControllerCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/AccountControllerCell.swift @@ -44,10 +44,7 @@ class AccountControllerCell: ThemeableCollectionViewListCell { iconView.cssSelectors = [.icon] disconnectButton.cssSelectors = [.disconnect] - logoFallbackView.contentMode = .scaleAspectFit - logoFallbackView.image = Branding.shared.brandedImageNamed(.bookmarkIcon) - - iconView.fallbackView = logoFallbackView + iconView.fallbackView = BrandView(showBackground: true, showLogo: true, logoMaxSize: CGSize(width: 36, height: 36), roundedCorners: true) titleLabel.font = UIFont.preferredFont(forTextStyle: .title3, with: .bold) titleLabel.adjustsFontForContentSizeCategory = true @@ -110,13 +107,15 @@ class AccountControllerCell: ThemeableCollectionViewListCell { disconnectButton.trailingAnchor.constraint(lessThanOrEqualTo: infoView.trailingAnchor), disconnectButton.centerYAnchor.constraint(equalTo: infoView.centerYAnchor), - contentView.heightAnchor.constraint(equalToConstant: AccountControllerCell.avatarSideLength + 20) + contentView.heightAnchor.constraint(equalToConstant: AccountControllerCell.avatarSideLength + 20).with(priority: .defaultHigh) ]) infoView.setContentHuggingPriority(.required, for: .horizontal) logoFallbackView.setContentHuggingPriority(.required, for: .vertical) titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) detailLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) + + disconnectButton.setContentCompressionResistancePriority(.required, for: .horizontal) } override init(frame: CGRect) { diff --git a/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell Content Providers/OCShare+UniversalItemListCellContentProvider.swift b/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell Content Providers/OCShare+UniversalItemListCellContentProvider.swift index 9d0d0fc43..5f7b7d246 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell Content Providers/OCShare+UniversalItemListCellContentProvider.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell Content Providers/OCShare+UniversalItemListCellContentProvider.swift @@ -88,7 +88,7 @@ extension OCShare: UniversalItemListCellContentProvider { } } else { // Link shares - content.title = .text(name ?? "Link".localized) + content.title = .text(name ?? token ?? "Link".localized) if let urlString = url?.absoluteString, urlString.count > 0 { if let roleName = matchingRole?.localizedName { @@ -242,7 +242,7 @@ extension OCShare: UniversalItemListCellContentProvider { } } - if category == .withMe, let effectiveState, effectiveState != .accepted { + if category == .withMe, let effectiveState { var accessories: [UICellAccessory] = [] let omitLongActions = (effectiveState == .pending) && (UITraitCollection.current.horizontalSizeClass == .compact) @@ -257,7 +257,7 @@ extension OCShare: UniversalItemListCellContentProvider { accessories.append(accessory) } - if effectiveState == .pending, !omitLongActions { + if (effectiveState == .pending || effectiveState == .accepted) && !omitLongActions { let (_, accessory) = cell.makeAccessoryButton(image: OCSymbol.icon(forSymbolName: "minus.circle"), title: "Decline".localized, accessibilityLabel: "Decline share".localized, cssSelectors: [.accessory, .decline], action: UIAction(handler: { [weak self, weak context] action in if let self, let context, let core = context.core { core.makeDecision(on: self, accept: false, completionHandler: { error in @@ -274,11 +274,11 @@ extension OCShare: UniversalItemListCellContentProvider { accessories.append(accessory) } - content.accessories = accessories - } + if effectiveState == .accepted { + accessories.append(cell.revealButtonAccessory) + } - if category == .withMe, let effectiveState, effectiveState == .accepted { - content.accessories = [ cell.revealButtonAccessory ] + content.accessories = accessories } _ = updateContent(content) diff --git a/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell Content Providers/OCShareRole+UniversalItemListCellContentProvider.swift b/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell Content Providers/OCShareRole+UniversalItemListCellContentProvider.swift index 433879709..74dc4c16b 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell Content Providers/OCShareRole+UniversalItemListCellContentProvider.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell Content Providers/OCShareRole+UniversalItemListCellContentProvider.swift @@ -25,6 +25,7 @@ extension OCShareRole: UniversalItemListCellContentProvider { if let icon = OCSymbol.icon(forSymbolName: symbolName) { content.icon = .icon(image: icon) + content.iconWidth = UniversalItemListCell.defaultIconSize.width / 2 } content.title = .text(localizedName) diff --git a/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell.swift b/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell.swift index 1ae92129f..92b36e21e 100644 --- a/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell.swift +++ b/ownCloudAppShared/Client/Collection Views/Cells/UniversalItemListCell.swift @@ -49,6 +49,7 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { icon = content.icon iconDisabled = content.iconDisabled + iconWidth = content.iconWidth details = content.details @@ -102,6 +103,7 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { var title: Title? var icon: Icon? var iconDisabled: Bool = false + var iconWidth: CGFloat? var details: [SegmentViewItem]? @@ -127,7 +129,7 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { return view }() - private let iconSize : CGSize = CGSize(width: 40, height: 40) + static public let defaultIconSize : CGSize = CGSize(width: 40, height: 40) public let thumbnailSize : CGSize = CGSize(width: 60, height: 60) // when changing size, also update .iconView.fallbackSize open var iconView: ResourceViewHost = ResourceViewHost(fallbackSize: CGSize(width: 60, height: 60)) // when changing size, also update .thumbnailSize @@ -306,10 +308,12 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { hasSecondaryDetailView = false } + let iconWidthConstraint = updateIconWidth(content?.iconWidth, defaultWidth: (iconViewHeight / 0.75)) // 4:3 + constraints = [ iconView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: horizontalMargin), iconView.trailingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: -spacing), - iconView.widthAnchor.constraint(equalToConstant: floor(iconViewHeight / 0.75)), // 4:3 + iconWidthConstraint, iconView.heightAnchor.constraint(equalToConstant: iconViewHeight), iconView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: verticalIconMargin), iconView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -verticalIconMargin), @@ -328,7 +332,7 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { let verticalLabelMargin : CGFloat = 10 let verticalIconMargin : CGFloat = 10 let spacing : CGFloat = 15 - let iconViewWidth : CGFloat = floor(iconSize.width / 2) + let iconViewWidth : CGFloat = floor(type(of: self).defaultIconSize.width / 2) let titleDetailSpacing: CGFloat = 15 titleLabel.numberOfLines = 1 @@ -341,10 +345,12 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { hasSecondaryDetailView = false } + let iconWidthConstraint = updateIconWidth(content?.iconWidth, defaultWidth: iconViewWidth) + constraints = [ iconView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: horizontalMargin), iconView.trailingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: -spacing), - iconView.widthAnchor.constraint(equalToConstant: iconViewWidth), + iconWidthConstraint, iconView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: verticalIconMargin), iconView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -verticalIconMargin), @@ -362,7 +368,7 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { let verticalLabelMargin : CGFloat = 10 let verticalIconMargin : CGFloat = 10 let spacing : CGFloat = 15 - let iconViewWidth : CGFloat = iconSize.width + let iconViewWidth : CGFloat = type(of: self).defaultIconSize.width let verticalLabelMarginFromCenter : CGFloat = 1 titleLabel.numberOfLines = 1 @@ -377,10 +383,12 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { truncationMode = .truncateTail + let iconWidthConstraint = updateIconWidth(content?.iconWidth, defaultWidth: iconViewWidth) + constraints = [ iconView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: horizontalMargin), iconView.trailingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: -spacing), - iconView.widthAnchor.constraint(equalToConstant: iconViewWidth), + iconWidthConstraint, iconView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: verticalIconMargin), iconView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -verticalIconMargin), @@ -406,6 +414,28 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { } } + private var iconWidthConstraint: NSLayoutConstraint? + private var lastIconWidth: CGFloat? + private var defaultIconWidthForCellLayout: CGFloat? // default width for current cell layout + private func updateIconWidth(_ newWidth: CGFloat?, defaultWidth: CGFloat? = nil) -> NSLayoutConstraint { + if let iconWidthConstraint { + iconWidthConstraint.isActive = false + } + + if let defaultWidth { + // Store default width for this cell type if one is provided + defaultIconWidthForCellLayout = defaultWidth + } + + // Fall back to default icon size if necessary + let effectiveWidth = newWidth ?? defaultIconWidthForCellLayout ?? type(of: self).defaultIconSize.width + + let widthConstraint = iconView.widthAnchor.constraint(equalToConstant: effectiveWidth) + iconWidthConstraint = widthConstraint + + return widthConstraint + } + // MARK: - Content var title: NSAttributedString? { didSet { @@ -502,6 +532,11 @@ open class UniversalItemListCell: ThemeableCollectionViewListCell { } } + if content?.iconWidth != lastIconWidth { + updateIconWidth(content?.iconWidth).isActive = true + lastIconWidth = content?.iconWidth + } + iconView.request = iconRequest if let iconViewProvider { iconView.activeViewProvider = iconViewProvider diff --git a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift index 3d9fa0168..f6bbff309 100644 --- a/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift +++ b/ownCloudAppShared/Client/Data Item Interactions/OCDataItem+InteractionProtocols.swift @@ -47,8 +47,8 @@ import ownCloudSDK // MARK: - Drag & drop public struct LocalDataItem { - var bookmarkUUID : UUID - var dataItem: OCDataItem + public var bookmarkUUID : UUID + public var dataItem: OCDataItem } @objc public protocol DataItemDragInteraction: OCDataItem { diff --git a/ownCloudAppShared/Client/Data Source Conditions/DataSourceCondition.swift b/ownCloudAppShared/Client/Data Source Conditions/DataSourceCondition.swift index d89bcfc02..3322f2e9a 100644 --- a/ownCloudAppShared/Client/Data Source Conditions/DataSourceCondition.swift +++ b/ownCloudAppShared/Client/Data Source Conditions/DataSourceCondition.swift @@ -94,7 +94,8 @@ open class DataSourceCondition: NSObject { setParentOf(condition: condition, to: self) action = inAction - if initial, let action { + if initial, let action, + fulfilled != nil { // only make initial call if a fulfilled state could already be computed - for condition types where this is not possible right away the subscription's `performInitialUpdate: true` parameter will make sure the initial call is performed as soon as fulfilled could be determined action(self) } } diff --git a/ownCloudAppShared/Client/Sharing/ShareViewController.swift b/ownCloudAppShared/Client/Sharing/ShareViewController.swift index 08cae769e..0eba7c02d 100644 --- a/ownCloudAppShared/Client/Sharing/ShareViewController.swift +++ b/ownCloudAppShared/Client/Sharing/ShareViewController.swift @@ -54,6 +54,7 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe public var share: OCShare? public var item: OCItem? public var location: OCLocation? + public var name: String? public var type: ShareType public var mode: Mode @@ -99,6 +100,8 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe var linksSectionDatasource: OCDataSourceArray? var linksSection: CollectionViewSection? + var nameTextField : UITextField? + var customPermissionsSectionOptionGroup: OptionGroup? var customPermissionsDatasource: OCDataSourceArray? var customPermissionsSection: CollectionViewSection? @@ -175,6 +178,31 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe sections.append(linksSection) } + // - Name + if self.type == .link { + let textField : UITextField = ThemeCSSTextField() + textField.translatesAutoresizingMaskIntoConstraints = false + textField.setContentHuggingPriority(.required, for: .vertical) + textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + textField.placeholder = share?.token ?? "Link".localized + textField.text = share?.name + textField.accessibilityLabel = "Name".localized + + nameTextField = textField + + let spacerView = UIView() + spacerView.translatesAutoresizingMaskIntoConstraints = false + spacerView.embed(toFillWith: textField, insets: NSDirectionalEdgeInsets(top: 10, leading: 18, bottom: 10, trailing: 18)) + + let nameSectionDatasource = OCDataSourceArray(items: [spacerView]) + let nameSection = CollectionViewSection(identifier: "name", dataSource: nameSectionDatasource, cellStyle: managementCellStyle, cellLayout: .list(appearance: .insetGrouped, contentInsets: .insetGroupedSectionInsets), clientContext: shareControllerContext) + nameSection.boundarySupplementaryItems = [ + .mediumTitle("Name".localized) + ] + + sections.append(nameSection) + } + // - Roles & permissions rolesSectionDatasource = OCDataSourceArray() @@ -264,6 +292,14 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe self.addStacked(child: bottomButtonBarViewController, position: .bottom) + // Wire up name textfield + self.name = share?.name + nameTextField?.addAction(UIAction(handler: { [weak self, weak nameTextField] _ in + if let nameTextField { + self?.name = nameTextField.text + } + }), for: .allEditingEvents) + // Set up view if let share, let core = clientContext?.core { role = core.matchingShareRole(for: share) @@ -664,7 +700,7 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe case .link: if let location, let permissions { - newShare = OCShare(publicLinkTo: location, linkName: nil, permissions: permissions, password: nil, expiration: nil) + newShare = OCShare(publicLinkTo: location, linkName: name, permissions: permissions, password: nil, expiration: nil) } } @@ -718,6 +754,10 @@ open class ShareViewController: CollectionViewController, SearchViewControllerDe share.protectedByPassword = true } + if self?.type == .link { + share.name = self?.name + } + share.expirationDate = self?.expirationDate }, completionHandler: { error, share in OnMainThread { diff --git a/ownCloudAppShared/Client/User Interface/MessageView.swift b/ownCloudAppShared/Client/User Interface/MessageView.swift index 4982cc3b7..2b01d6930 100644 --- a/ownCloudAppShared/Client/User Interface/MessageView.swift +++ b/ownCloudAppShared/Client/User Interface/MessageView.swift @@ -88,6 +88,7 @@ open class MessageView: UIView { messageLabel.translatesAutoresizingMaskIntoConstraints = false messageLabel.numberOfLines = 0 messageLabel.textAlignment = .center + messageLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) containerView.addSubview(imageView) containerView.addSubview(titleLabel) diff --git a/ownCloudAppShared/Client/User Interface/NamingViewController.swift b/ownCloudAppShared/Client/User Interface/NamingViewController.swift index 7aff8f858..0f457a0e5 100644 --- a/ownCloudAppShared/Client/User Interface/NamingViewController.swift +++ b/ownCloudAppShared/Client/User Interface/NamingViewController.swift @@ -19,8 +19,8 @@ import UIKit import ownCloudSDK -public typealias StringValidatorResult = (Bool, String?, String?) -public typealias StringValidatorHandler = (String) -> StringValidatorResult +public typealias StringValidatorResult = (Bool, String?, String?) // (validationPassed, validationErrorTitle, validationErrorMessage) +public typealias StringValidatorHandler = (_ stringToCheck: String) -> StringValidatorResult open class NamingViewController: UIViewController { weak open var item: OCItem? @@ -28,6 +28,7 @@ open class NamingViewController: UIViewController { open var completion: (String?, NamingViewController) -> Void open var stringValidator: StringValidatorHandler? open var defaultName: String? + open var requiredFileExtension: String? private var blurView: UIVisualEffectView @@ -37,7 +38,7 @@ open class NamingViewController: UIViewController { private var thumbnailImageView: ResourceViewHost private var nameContainer: UIView - private var nameTextField: UITextField + private var nameTextField: ThemeCSSTextField private var textfieldTopAnchorConstraint: NSLayoutConstraint private var textfieldCenterYAnchorConstraint: NSLayoutConstraint @@ -152,6 +153,7 @@ open class NamingViewController: UIViewController { // Name textfield nameTextField.translatesAutoresizingMaskIntoConstraints = false + nameTextField.requiredFileExtension = requiredFileExtension nameContainer.addSubview(nameTextField) NSLayoutConstraint.activate([ nameTextField.heightAnchor.constraint(equalToConstant: 40), @@ -267,7 +269,9 @@ open class NamingViewController: UIViewController { } @objc open func textfieldDidChange(_ sender: UITextField) { - if sender.text != "" { + let filename = sender.text + + if filename != "", requiredFileExtension == nil || ((requiredFileExtension != nil) && filename != ".\(requiredFileExtension!)") { doneButton?.isEnabled = true } else { doneButton?.isEnabled = false @@ -351,7 +355,15 @@ extension NamingViewController: UITextFieldDelegate { } else { textField.selectedTextRange = nameTextField.textRange(from: nameTextField.beginningOfDocument, to: nameTextField.endOfDocument) } + } + public func textFieldShouldClear(_ textField: UITextField) -> Bool { + if let requiredFileExtension { + textField.text = "." + requiredFileExtension + textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.beginningOfDocument) + return false + } + return true } open func textFieldShouldReturn(_ textField: UITextField) -> Bool { diff --git a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift index a255c3b47..990cd564b 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift +++ b/ownCloudAppShared/Client/View Controllers/ClientItemViewController.swift @@ -673,33 +673,64 @@ open class ClientItemViewController: CollectionViewController, SortBarDelegate, } set { + if navigationItem.titleView is ClientLocationPopupButton { + navigationItem.titleView = nil + } + navigationItem.titleLabelText = newValue navigationItem.title = newValue } } + var useNavigationLocationBreadcrumbDropdown: Bool { + return UIDevice.current.userInterfaceIdiom == .pad + } + + var navigationLocation: OCLocation? { + didSet { + if let clientContext, let navigationLocation, !navigationLocation.isRoot { + navigationItem.titleView = ClientLocationPopupButton(clientContext: clientContext, location: navigationLocation) + } else { + if navigationItem.titleView is ClientLocationPopupButton { + navigationItem.titleView = nil + } + } + } + } + func updateNavigationTitleFromContext() { var navigationTitle: String? + var navigationLocation: OCLocation? // Set navigation title from location (if provided) if let location { navigationTitle = location.displayName(in: clientContext) + navigationLocation = location } // Set navigation title from queryLocation if navigationTitle == nil, let queryLocation = query?.queryLocation { navigationTitle = queryLocation.displayName(in: clientContext) + navigationLocation = queryLocation } // Set navigation title from rootItem.name - if navigationTitle == nil { - navigationTitle = (self.clientContext?.rootItem as? OCItem)?.name + if navigationTitle == nil, let queryRootItem = self.clientContext?.rootItem as? OCItem { + navigationTitle = queryRootItem.name + navigationLocation = queryRootItem.location } - if let navigationTitle { - self.navigationTitle = navigationTitle - } else { - self.navigationTitle = navigationItem.title + // Compose navigation title + if useNavigationLocationBreadcrumbDropdown { + self.navigationLocation = navigationLocation + } + + if navigationLocation == nil || !useNavigationLocationBreadcrumbDropdown { + if let navigationTitle { + self.navigationTitle = navigationTitle + } else { + self.navigationTitle = navigationItem.title + } } } diff --git a/ownCloudAppShared/Client/View Controllers/ClientSidebarViewController.swift b/ownCloudAppShared/Client/View Controllers/ClientSidebarViewController.swift index ce0730e25..fd67f1c73 100644 --- a/ownCloudAppShared/Client/View Controllers/ClientSidebarViewController.swift +++ b/ownCloudAppShared/Client/View Controllers/ClientSidebarViewController.swift @@ -71,14 +71,26 @@ public class ClientSidebarViewController: CollectionSidebarViewController, Navig accountsControllerSectionSource?.source = OCBookmarkManager.shared.bookmarksDatasource // Combined data source - if let accountsControllerSectionSource, let sidebarLinksDataSource = sidebarLinksDataSource { - combinedSectionsDatasource = OCDataSourceComposition(sources: [ accountsControllerSectionSource, sidebarLinksDataSource ]) + if let accountsControllerSectionSource { + var sources: [OCDataSource] = [ accountsControllerSectionSource ] + + if let brandingElementDataSource { + sources.insert(brandingElementDataSource, at: 0) + } + + if let sidebarLinksDataSource { + sources.append(sidebarLinksDataSource) + } + + if sources.count > 1 { + combinedSectionsDatasource = OCDataSourceComposition(sources: sources) + } } // Set up Collection View sectionsDataSource = combinedSectionsDatasource ?? accountsControllerSectionSource navigationItem.largeTitleDisplayMode = .never - navigationItem.titleView = self.buildNavigationLogoView() + navigationItem.titleView = ClientSidebarViewController.buildNavigationLogoView() // Add 10pt space at the top so that the first section's account doesn't "stick" to the top collectionView.contentInset.top += 10 @@ -170,6 +182,24 @@ public class ClientSidebarViewController: CollectionSidebarViewController, Navig focusedBookmark = newFocusedBookmark } + public var brandingElementDataSource: OCDataSourceArray? { + if Branding.shared.isBranded { + let logoSize = CGSize(width: 128, height: 64) + let brandView = BrandView(showBackground: true, showLogo: true, logoMaxSize: logoSize, roundedCorners: true, assetSuffix: .sidebar) + + NSLayoutConstraint.activate([ + brandView.heightAnchor.constraint(equalToConstant: logoSize.height) + ]) + + let elementDataSource = OCDataSourceArray(items: [ brandView ]) + let section = CollectionViewSection(identifier: "branding-elements", dataSource: elementDataSource, cellStyle: CollectionViewCellStyle(with: .sideBar), cellLayout: .list(appearance: .sidebar), clientContext: clientContext) + + return OCDataSourceArray(items: [ section ]) + } + + return nil + } + public var sidebarLinksDataSource: OCDataSourceArray? { if let sidebarLinks = Branding.shared.sidebarLinks { let actions = sidebarLinks.compactMap { link in @@ -206,11 +236,54 @@ public class ClientSidebarViewController: CollectionSidebarViewController, Navig return nil } + + // MARK: - Reordering bookmarks + func dataItem(for itemRef: CollectionViewController.ItemRef) -> OCDataItem? { + let (dataItemRef, sectionID) = unwrap(itemRef) + + if let sectionID, let section = sectionsByID[sectionID] { + if let record = try? section.dataSource?.record(forItemRef: dataItemRef) { + return record.item + } + } + + return nil + } + + public override func configureDataSource() { + super.configureDataSource() + + collectionViewDataSource.reorderingHandlers.canReorderItem = { (itemRef) in + // Log.debug("Can reorder \(itemRef)") + return true + } + + collectionViewDataSource.reorderingHandlers.didReorder = { [weak self] transaction in + Log.debug("Did reorder \(transaction)") + + guard let self else { return } + + var reorderedBookmarks: [OCBookmark] = [] + + for collectionItemRef in transaction.finalSnapshot.itemIdentifiers { + if let accountController = self.dataItem(for: collectionItemRef) as? AccountController, + let bookmark = accountController.bookmark, + let managedBookmark = OCBookmarkManager.shared.bookmark(for: bookmark.uuid) { + reorderedBookmarks.append(managedBookmark) + Log.debug("Bookmark: \(bookmark.shortName)") + } + } + + if OCBookmarkManager.shared.bookmarks.count == reorderedBookmarks.count { + OCBookmarkManager.shared.replaceBookmarks(reorderedBookmarks) + } + } + } } // MARK: - Branding extension ClientSidebarViewController { - func buildNavigationLogoView() -> ThemeCSSView { + static public func buildNavigationLogoView() -> ThemeCSSView { let logoImage = UIImage(named: "branding-login-logo") let logoImageView = UIImageView(image: logoImage) logoImageView.cssSelector = .icon @@ -231,7 +304,6 @@ extension ClientSidebarViewController { let logoContainer = ThemeCSSView(withSelectors: [.logo]) logoContainer.translatesAutoresizingMaskIntoConstraints = false - logoContainer.addSubview(logoImageView) logoContainer.setContentHuggingPriority(.required, for: .horizontal) logoContainer.setContentHuggingPriority(.required, for: .vertical) @@ -239,16 +311,18 @@ extension ClientSidebarViewController { logoWrapperView.addSubview(logoContainer) if VendorServices.shared.isBranded { + logoContainer.addSubview(logoLabel) NSLayoutConstraint.activate([ - logoImageView.topAnchor.constraint(greaterThanOrEqualTo: logoContainer.topAnchor), - logoImageView.bottomAnchor.constraint(lessThanOrEqualTo: logoContainer.bottomAnchor), - logoImageView.centerYAnchor.constraint(equalTo: logoContainer.centerYAnchor), - logoImageView.centerXAnchor.constraint(equalTo: logoContainer.centerXAnchor), + logoLabel.topAnchor.constraint(greaterThanOrEqualTo: logoContainer.topAnchor), + logoLabel.bottomAnchor.constraint(lessThanOrEqualTo: logoContainer.bottomAnchor), + logoLabel.centerYAnchor.constraint(equalTo: logoContainer.centerYAnchor), + logoLabel.centerXAnchor.constraint(equalTo: logoContainer.centerXAnchor), logoContainer.topAnchor.constraint(equalTo: logoWrapperView.topAnchor), logoContainer.bottomAnchor.constraint(equalTo: logoWrapperView.bottomAnchor), logoContainer.centerXAnchor.constraint(equalTo: logoWrapperView.centerXAnchor) ]) } else { + logoContainer.addSubview(logoImageView) logoContainer.addSubview(logoLabel) NSLayoutConstraint.activate([ logoImageView.topAnchor.constraint(greaterThanOrEqualTo: logoContainer.topAnchor), diff --git a/ownCloudAppShared/Client/View Controllers/Location Breadcrumbs/ClientLocationPopupButton.swift b/ownCloudAppShared/Client/View Controllers/Location Breadcrumbs/ClientLocationPopupButton.swift new file mode 100644 index 000000000..03ffaa528 --- /dev/null +++ b/ownCloudAppShared/Client/View Controllers/Location Breadcrumbs/ClientLocationPopupButton.swift @@ -0,0 +1,90 @@ +// +// ClientLocationPopupButton.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 23.10.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +open class ClientLocationPopupButton: ThemeCSSButton { + weak var clientContext: ClientContext? + open var location: OCLocation? { + didSet { + updateButton() + } + } + + public init(clientContext: ClientContext? = nil, location: OCLocation? = nil, excludeLastPathComponent: Bool = true) { + super.init(frame: .zero) + cssSelectors = [.title] + + self.clientContext = clientContext + self.location = location + + titleLabel?.adjustsFontForContentSizeCategory = true + semanticContentAttribute = (effectiveUserInterfaceLayoutDirection == .leftToRight) ? .forceRightToLeft : .forceLeftToRight + setContentHuggingPriority(.defaultHigh, for: .horizontal) + setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + showsMenuAsPrimaryAction = true + translatesAutoresizingMaskIntoConstraints = false + + menu = UIMenu(title: "", children: [ + UIDeferredMenuElement.uncached({ [weak self] completion in + var menuItems : [UIMenuElement] = [] + let breadcrumbLocation = excludeLastPathComponent ? self?.location?.parent : self?.location + + if let clientContext = self?.clientContext, let breadcrumbs = breadcrumbLocation?.breadcrumbs(in: clientContext, includeServerName: false, action: .reveal).reversed() { + for crumbAction in breadcrumbs { + menuItems.append(crumbAction.uiAction()) + } + } + + completion(menuItems) + }) + ]) + + updateButton() + } + + open override func didMoveToWindow() { + super.didMoveToWindow() + + self.updateButton() + } + + func updateButton() { + let title = location?.displayName(in: clientContext) ?? "-" + let attributedTitle = AttributedString(NSAttributedString(string: title, attributes: [.font : UIFont.systemFont(ofSize: UIFont.buttonFontSize, weight: .semibold)])) + let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 0.7 * UIFont.buttonFontSize) + let chevronBackgroundColor = Theme.shared.activeThemeCSS.getColor(.fill, selectors: [.popupButton, .icon], for: self) ?? .lightGray + let chevronForegroundColor = Theme.shared.activeThemeCSS.getColor(.stroke, selectors: [.popupButton, .icon], for: self) ?? .tintColor + let chevronImage = UIImage(systemName: "chevron.down.circle.fill", withConfiguration: symbolConfiguration)?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(paletteColors: [chevronForegroundColor, chevronBackgroundColor])) + + var buttonConfig = configuration ?? .plain() + buttonConfig.imagePadding = 5 + buttonConfig.attributedTitle = attributedTitle + #if swift(>=5.9) // workaround build issue on Xcode 14.2 (GitHub actions) + buttonConfig.titleLineBreakMode = .byTruncatingMiddle + #endif + buttonConfig.image = chevronImage + + self.configuration = buttonConfig + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ownCloudAppShared/Client/View Controllers/Location Breadcrumbs/OCLocation+Breadcrumbs.swift b/ownCloudAppShared/Client/View Controllers/Location Breadcrumbs/OCLocation+Breadcrumbs.swift index 99313a4e8..25752a674 100644 --- a/ownCloudAppShared/Client/View Controllers/Location Breadcrumbs/OCLocation+Breadcrumbs.swift +++ b/ownCloudAppShared/Client/View Controllers/Location Breadcrumbs/OCLocation+Breadcrumbs.swift @@ -24,6 +24,12 @@ public extension OCActionPropertyKey { } public extension OCLocation { + enum BreadcrumbAction { + case open + case reveal + case auto + } + func displayName(in context: ClientContext?) -> String { switch type { case .drive: @@ -82,21 +88,68 @@ public extension OCLocation { return nil } - func breadcrumbs(in clientContext: ClientContext, includeServerName: Bool = true, includeDriveName: Bool = true) -> [OCAction] { + func breadcrumbs(in clientContext: ClientContext, includeServerName: Bool = true, includeDriveName: Bool = true, skipFiles: Bool = false, action breadcrumbAction: BreadcrumbAction = .open) -> [OCAction] { var breadcrumbs: [OCAction] = [] var currentLocation = self + var previousLocation: OCLocation? + var effectiveIncludeServername = includeServerName func addCrumb(title: String?, icon: UIImage?, location: OCLocation? = nil) { var actionBlock: OCActionBlock? if let location { - actionBlock = { [weak clientContext] (action, options, completion) in + if location.type == .file, skipFiles { + previousLocation = location + return + } + + actionBlock = { [weak clientContext, previousLocation] (action, options, completion) in if let context = (options?[.clientContext] as? ClientContext) ?? clientContext { - _ = (location as DataItemSelectionInteraction).openItem?(from: nil, with: context, animated: true, pushViewController: true, completion: { (success) in - completion(success ? nil : NSError(ocError: .internal)) - }) + var effectiveBreadcrumbAction: BreadcrumbAction = breadcrumbAction + + switch breadcrumbAction { + case .open: break + + case .reveal: + if let previousLocation { + if previousLocation.type != .folder, previousLocation.type != .file { + // Can only reveal files and folders + effectiveBreadcrumbAction = .open + } + } else { + if location.type != .file { + // Only reveal top file, but not folder + effectiveBreadcrumbAction = .open + } + } + + case .auto: + if location.type == .file { + effectiveBreadcrumbAction = .reveal + } else { + effectiveBreadcrumbAction = .open + } + } + + switch effectiveBreadcrumbAction { + case .open: + _ = (location as DataItemSelectionInteraction).openItem?(from: nil, with: context, animated: true, pushViewController: true, completion: { (success) in + completion(success ? nil : NSError(ocError: .internal)) + }) + + case .reveal: + let revealLocation = previousLocation ?? location + + _ = (revealLocation as DataItemSelectionInteraction).revealItem?(from: nil, with: context, animated: true, pushViewController: true, completion: { success in + completion(success ? nil : NSError(ocError: .internal)) + }) + + case .auto: break + } } } + + previousLocation = location } let action = OCAction(title: title ?? "?", icon: icon, action: actionBlock) @@ -105,10 +158,19 @@ public extension OCLocation { breadcrumbs.insert(action, at: 0) } +// // Include file +// if currentLocation.type == .file { +// addCrumb(title: currentLocation.displayName(in: clientContext), icon: currentLocation.displayIcon(in: clientContext), location: currentLocation) +// +// if let parentLocation = currentLocation.parent { +// currentLocation = parentLocation +// } +// } + // Location in reverse - if currentLocation.type == .folder { + if currentLocation.type == .folder || currentLocation.type == .file { while !currentLocation.isRoot, currentLocation.path != nil { - if currentLocation.type == .folder { + if currentLocation.type == .folder || currentLocation.type == .file { addCrumb(title: currentLocation.displayName(in: clientContext), icon: currentLocation.displayIcon(in: clientContext), location: currentLocation) } @@ -121,13 +183,23 @@ public extension OCLocation { } // Drive name - if let driveID = self.driveID, includeDriveName { - let location = OCLocation(driveID: driveID, path: "/") - addCrumb(title: location.displayName(in: clientContext), icon: location.displayIcon(in: clientContext), location: location) + if includeDriveName { + var location: OCLocation? + + if let driveID = self.driveID { + location = OCLocation(driveID: driveID, path: "/") + } else { + // avoid duplicate OC10 root breadcrumb when the server name is also included + effectiveIncludeServername = true // alternative would be: location = .legacyRoot - but then there's Files /and/ the server name in the breadcrumbs if includeServername already == true; so for consistency, instead of a "Files" breadcrumb the server name is shown + } + + if let location { + addCrumb(title: location.displayName(in: clientContext), icon: location.displayIcon(in: clientContext), location: location) + } } // Server name - if let bookmark = clientContext.core?.bookmark, includeServerName { + if let bookmark = clientContext.core?.bookmark, effectiveIncludeServername { addCrumb(title: bookmark.displayName ?? bookmark.shortName, icon: OCSymbol.icon(forSymbolName: "server.rack"), location: (self.driveID == nil) ? OCLocation.legacyRoot : nil) } diff --git a/ownCloudAppShared/SDK Extensions/OCAction+UIAction.swift b/ownCloudAppShared/SDK Extensions/OCAction+UIAction.swift new file mode 100644 index 000000000..afedd6db5 --- /dev/null +++ b/ownCloudAppShared/SDK Extensions/OCAction+UIAction.swift @@ -0,0 +1,28 @@ +// +// OCAction+UIAction.swift +// ownCloudAppShared +// +// Created by Felix Schwarz on 23.10.23. +// Copyright © 2023 ownCloud GmbH. All rights reserved. +// + +/* + * Copyright (C) 2023, ownCloud GmbH. + * + * This code is covered by the GNU Public License Version 3. + * + * For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + * You should have received a copy of this license along with this program. If not, see . + * + */ + +import UIKit +import ownCloudSDK + +extension OCAction { + func uiAction(with options: [OCActionRunOptionKey : Any]? = nil) -> UIAction { + return UIAction(title: title, image: icon, attributes: [], handler: { action in + self.run(options: options) + }) + } +} diff --git a/ownCloudAppShared/UIKit Extension/UIView+Extension.swift b/ownCloudAppShared/UIKit Extension/UIView+Extension.swift index b2d6f6b3d..b281fd66f 100644 --- a/ownCloudAppShared/UIKit Extension/UIView+Extension.swift +++ b/ownCloudAppShared/UIKit Extension/UIView+Extension.swift @@ -60,4 +60,20 @@ public extension UIView { return nil } + + // MARK: - View controller + var hostingViewController: UIViewController? { + var responder: UIResponder? = self + var hostViewController: UIViewController? + + while hostViewController == nil && responder != nil { + if let viewController = responder as? UIViewController { + hostViewController = viewController + } + + responder = responder?.next + } + + return hostViewController + } } diff --git a/ownCloudAppShared/User Interface/SegmentView/UIView+EmbedAndLayout.swift b/ownCloudAppShared/User Interface/SegmentView/UIView+EmbedAndLayout.swift index 4a74781d6..0820602e5 100644 --- a/ownCloudAppShared/User Interface/SegmentView/UIView+EmbedAndLayout.swift +++ b/ownCloudAppShared/User Interface/SegmentView/UIView+EmbedAndLayout.swift @@ -23,19 +23,19 @@ public extension UIView { typealias ConstraintsModifier = (_ constraintSet: ConstraintSet) -> ConstraintSet struct ConstraintSet { - var firstLeadingOrTopConstraint: NSLayoutConstraint? - var lastTrailingOrBottomConstraint: NSLayoutConstraint? + public var firstLeadingOrTopConstraint: NSLayoutConstraint? + public var lastTrailingOrBottomConstraint: NSLayoutConstraint? } struct AnchorSet { - var leadingAnchor: NSLayoutXAxisAnchor - var trailingAnchor: NSLayoutXAxisAnchor + public var leadingAnchor: NSLayoutXAxisAnchor + public var trailingAnchor: NSLayoutXAxisAnchor - var topAnchor: NSLayoutYAxisAnchor - var bottomAnchor: NSLayoutYAxisAnchor + public var topAnchor: NSLayoutYAxisAnchor + public var bottomAnchor: NSLayoutYAxisAnchor - var centerXAnchor: NSLayoutXAxisAnchor - var centerYAnchor: NSLayoutYAxisAnchor + public var centerXAnchor: NSLayoutXAxisAnchor + public var centerYAnchor: NSLayoutYAxisAnchor } var defaultAnchorSet : AnchorSet { @@ -116,7 +116,7 @@ public extension UIView { return constraintSet } - @discardableResult func embedVertically(views: [UIView], insets: NSDirectionalEdgeInsets, enclosingAnchors: AnchorSet? = nil, spacingProvider: SpacingProvider? = nil, constraintsModifier: ConstraintsModifier? = nil) -> ConstraintSet { + @discardableResult func embedVertically(views: [UIView], insets: NSDirectionalEdgeInsets, enclosingAnchors: AnchorSet? = nil, spacingProvider: SpacingProvider? = nil, centered: Bool = true, constraintsModifier: ConstraintsModifier? = nil) -> ConstraintSet { var viewIdx : Int = 0 var previousView: UIView? var embedConstraints: [NSLayoutConstraint] = [] @@ -143,11 +143,18 @@ public extension UIView { } // - horizontal position + insets - embedConstraints.append(contentsOf: [ - view.centerXAnchor.constraint(equalTo: anchorSet.centerXAnchor), - view.leadingAnchor.constraint(greaterThanOrEqualTo: anchorSet.leadingAnchor, constant: insets.leading), - view.trailingAnchor.constraint(lessThanOrEqualTo: anchorSet.trailingAnchor, constant: -insets.trailing) - ]) + if centered { + embedConstraints.append(contentsOf: [ + view.centerXAnchor.constraint(equalTo: anchorSet.centerXAnchor), + view.leadingAnchor.constraint(greaterThanOrEqualTo: anchorSet.leadingAnchor, constant: insets.leading), + view.trailingAnchor.constraint(lessThanOrEqualTo: anchorSet.trailingAnchor, constant: -insets.trailing) + ]) + } else { + embedConstraints.append(contentsOf: [ + view.leadingAnchor.constraint(equalTo: anchorSet.leadingAnchor, constant: insets.leading), + view.trailingAnchor.constraint(equalTo: anchorSet.trailingAnchor, constant: -insets.trailing) + ]) + } // - bottom if viewIdx == (views.count-1) { @@ -194,10 +201,12 @@ public extension UIView { return constraints } - @discardableResult func embed(centered view: UIView, minimumInsets insets: NSDirectionalEdgeInsets = .zero, fixedSize: CGSize? = nil, minimumSize: CGSize? = nil, maximumSize: CGSize? = nil, enclosingAnchors: AnchorSet? = nil) -> [NSLayoutConstraint] { - view.translatesAutoresizingMaskIntoConstraints = false + @discardableResult func embed(centered view: UIView, minimumInsets insets: NSDirectionalEdgeInsets = .zero, fixedSize: CGSize? = nil, minimumSize: CGSize? = nil, maximumSize: CGSize? = nil, enclosingAnchors: AnchorSet? = nil, constraintsOnly: Bool = false) -> [NSLayoutConstraint] { + if !constraintsOnly { + view.translatesAutoresizingMaskIntoConstraints = false - addSubview(view) + addSubview(view) + } var constraints: [NSLayoutConstraint] let anchorSet = enclosingAnchors ?? defaultAnchorSet @@ -232,7 +241,9 @@ public extension UIView { ] } - NSLayoutConstraint.activate(constraints) + if !constraintsOnly { + NSLayoutConstraint.activate(constraints) + } return constraints } diff --git a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift index aabbad6fa..ff607e669 100644 --- a/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift +++ b/ownCloudAppShared/User Interface/StaticTableView/StaticTableViewRow.swift @@ -528,6 +528,21 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate { return true } + public func textFieldShouldClear(_ textField: UITextField) -> Bool { + if let requiredFileExtension { + textField.text = "." + requiredFileExtension + textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.beginningOfDocument) + return false + } + return true + } + + open var requiredFileExtension: String? { + didSet { + (textField as? ThemeCSSTextField)?.requiredFileExtension = requiredFileExtension + } + } + // MARK: - Labels convenience public init(label: String, accessoryView: UIView? = nil, identifier: String? = nil) { self.init() diff --git a/ownCloudAppShared/User Interface/Theme/CSS/README.md b/ownCloudAppShared/User Interface/Theme/CSS/README.md index aeb32303f..70f47ed03 100644 --- a/ownCloudAppShared/User Interface/Theme/CSS/README.md +++ b/ownCloudAppShared/User Interface/Theme/CSS/README.md @@ -113,7 +113,7 @@ getInteger | `Int` getCGFloat | `CGFloat` getBool | `Boolean`, `String` (`true` and `false`) getUserInterfaceStyle | `UIUserInterfaceStyle`, `Int`, `String` (`unspecified`, `light`, `dark`) -getStatusBarStyle | `UIStatusBarStyle`, `Int`, `String` (`default`, `lightContent`, `darkContent`) +getStatusBarStyle | `UIStatusBarStyle`, `Int`, `String` (`default`, `lightContent`, `darkContent`, `white`, `black`) getBarStyle | `UIBarStyle`, `Int`, `String` (`default`, `black`) getKeyboardAppearance | `UIKeyboardAppearance`, `Int`, `String` (`default`, `light`, `dark`) getActivityIndicatorStyle | `UIActivityIndicatorView.Style`, `Int`, `String` (`medium`, `large`) @@ -187,33 +187,108 @@ Matching: A way to change the value of the `stroke` property, then, would be to add a more specific record, f.ex. for `collection.cell.sortBar` - or possibly for `sortBar` directly (the last selector is weighted much higher). ## Adding styling via branding -Additional CSS records can be added through the usual branding options, including `Branding.plist`, by providing an array of css records following this format: +Depending on how a client is branded, different options are used to add CSS records to f.ex. the `Branding.plist`. + +In all cases, the additional CSS records follow this format: ``` selector1.selector2….property: value ``` -Example for a `Branding.plist` that fills the logo in the sidebar's navigation bar with red color: +See *Debugging selectors and matching* > *Usage in practice* above for how to determine the selectors of a view on screen. + +### Branding on top of default themes +If `branding.theme-definitions` is **not** used, the app provides themes for light mode and dark mode to branded clients, with support for light customization through branding: +Option | Description +--|-- +`branding.theme-colors`| Values to use in system-color-based themes for branded clients. Mutually exclusive with theme-definitions. +`branding.theme-css-records` | CSS records to add to the CSS space of system-color-based themes for branded clients. Mutually exclusive with theme-definitions. + +Both options can be used together. + +#### Using `branding.theme-css-records` +Example for a `Branding.plist` filling the logo in the sidebar's navigation bar with red and the account pill with green: ```xml -branding.theme-definitions$[0].cssRecords +branding.theme-css-records sidebar.navigationBar.logo.stroke: #ff0000 + sidebar.account.fill: #00ff00 ``` -See *Debugging selectors and matching* > *Usage in practice* above for how to determine the selectors of a view on screen. +Example in flat notation: +```xml +branding.theme-css-records$[0] +sidebar.navigationBar.logo.stroke: #ff0000 +branding.theme-css-records$[1] +sidebar.account.fill: #00ff00 +``` -#### Icon color CSS selectors -Icon colors are now also configured via CSS selectors: +#### Using `branding.theme-colors` +For commonly branded elements, the app supports the following aliases to be used in key-value pairs in `branding.theme-colors`: -TVG/Legacy Color | CSS selector string -------------------|------------------------------------ -`folderFillColor` | `vectorImage.folderColor.fill` -`fileFillColor` | `vectorImage.fileColor.fill` -`logoFillColor` | `vectorImage.logoColor.fill` -`iconFillColor` | `vectorImage.iconColor.fill` -`symbolFillColor` | `vectorImage.symbolColor.fill` +Alias | Value +--|-- +`tint-color` | Color to use as tint/accent color for controls (in hex notation). Replaces `branding.theme-tint-color`. +`branding-background-color` | Color to use as background color for brand views (in hex notation). +`setup-status-bar-style` | The status bar style in the setup wizard, affecting the status bar text color. Can be either `default`, `black` or `white`. +`file-icon-color` | Color to fill file icons with (in hex notation). +`folder-icon-color` | Color to fill folder icons with (in hex notation). + +Each alias can be expanded to one or more CSS addresses internally, so that the values set here can be assigned to the right elements - even after major UI changes or refactoring. + +Example: +```xml +branding.theme-colors + + tint-color + #ff0000 + branding-background-color + #0ff0f0 + setup-status-bar-style + black + folder-icon-color + #00ff00 + file-icon-color + #0000ff + +```` + +Example in flat notation: +```xml +branding.theme-colors$tint-color +#ff0000 +branding.theme-colors$branding-background-color +#0ff0f0 +branding.theme-colors$setup-status-bar-style +black +branding.theme-colors$folder-icon-color +#00ff00 +branding.theme-colors$file-icon-color +#0000ff +```` + +### Branding with fully custom themes +For clients branded with fully custom themes via `branding.theme-definitions`, an array of additional CSS records can be added for each theme definition. + +Example for a `Branding.plist` filling the logo in the sidebar's navigation bar with red and the account pill with green: + +```xml +branding.theme-definitions$[0].cssRecords + + sidebar.navigationBar.logo.stroke: #ff0000 + sidebar.account.fill: #00ff00 + +``` + +Example in flat notation: +```xml +branding.theme-definitions$[0].cssRecords[0] +sidebar.navigationBar.logo.stroke: #ff0000 +branding.theme-definitions$[0].cssRecords[1] +sidebar.account.fill: #00ff00 +``` ### Reference #### Selectors @@ -234,7 +309,18 @@ Property | Type | Description / Values `cornerRadius` | float | Corner radius (not widely used) `style` | `UIUserInterfaceStyle` | `unspecified`, `light`, `dark` `barStyle` | `UIBarStyle` | `default`, `black` -`statusBarStyle` | `UIStatusBarStyle` | `default`, `lightContent`, `darkContent` +`statusBarStyle` | `UIStatusBarStyle` | `default`, `lightContent`, `darkContent`, `black`, `white` `blurEffectStyle` | `UIBlurEffect.Style` | `regular`, `light`, `dark` `keyboardAppearance` | `UIKeyboardAppearance` | `default`, `light`, `dark` `activityIndicatorStyle` | `UIActivityIndicatorView.Style` | `medium`, `large` + +#### Icon color CSS selectors +Icon colors are now also configured via CSS selectors: + +TVG/Legacy Color | CSS selector string +------------------|------------------------------------ +`folderFillColor` | `vectorImage.folderColor.fill` +`fileFillColor` | `vectorImage.fileColor.fill` +`logoFillColor` | `vectorImage.logoColor.fill` +`iconFillColor` | `vectorImage.iconColor.fill` +`symbolFillColor` | `vectorImage.symbolColor.fill` diff --git a/ownCloudAppShared/User Interface/Theme/CSS/ThemeCSS.swift b/ownCloudAppShared/User Interface/Theme/CSS/ThemeCSS.swift index aabb1e872..46f50cde7 100644 --- a/ownCloudAppShared/User Interface/Theme/CSS/ThemeCSS.swift +++ b/ownCloudAppShared/User Interface/Theme/CSS/ThemeCSS.swift @@ -317,6 +317,8 @@ open class ThemeCSS: NSObject { case "default": return .default case "lightContent": return .lightContent case "darkContent": return .darkContent + case "white": return .lightContent + case "black": return .darkContent default: break } } diff --git a/ownCloudAppShared/User Interface/Theme/CSS/Views/ThemeCSSTextField.swift b/ownCloudAppShared/User Interface/Theme/CSS/Views/ThemeCSSTextField.swift index d22b6e34c..4b37e38e7 100644 --- a/ownCloudAppShared/User Interface/Theme/CSS/Views/ThemeCSSTextField.swift +++ b/ownCloudAppShared/User Interface/Theme/CSS/Views/ThemeCSSTextField.swift @@ -18,9 +18,20 @@ import UIKit +public extension UITextInput { + func range(from textRange: UITextRange) -> NSRange { + let startOffset = offset(from: beginningOfDocument, to: textRange.start) + let endOffset = offset(from: beginningOfDocument, to: textRange.end) + + return NSRange(location: startOffset, length: endOffset-startOffset+1) + } +} + public class ThemeCSSTextField: UITextField, Themeable { private var hasRegistered : Bool = false + open var requiredFileExtension: String? + override open func didMoveToWindow() { super.didMoveToWindow() @@ -41,4 +52,26 @@ public class ThemeCSSTextField: UITextField, Themeable { open func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) { self.applyThemeCollection(collection) } + + public override func shouldChangeText(in range: UITextRange, replacementText: String) -> Bool { + if let requiredFileExtension, let previousText = text, + let textRange = Range(self.range(from: range), in: previousText) { + let newName = previousText.replacingCharacters(in: textRange, with: replacementText) + return (newName as NSString).pathExtension == requiredFileExtension || (newName == ".\(requiredFileExtension)") + } + + return super.shouldChangeText(in: range, replacementText: replacementText) + } + + public override var selectedTextRange: UITextRange? { + didSet { + if let requiredFileExtension { + if let selectedTextRange, + let maxAllowedPosition = position(from: endOfDocument, offset: -requiredFileExtension.count-1), + compare(selectedTextRange.end, to: maxAllowedPosition) == .orderedDescending { + self.selectedTextRange = textRange(from: selectedTextRange.start, to: maxAllowedPosition) + } + } + } + } } diff --git a/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift b/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift index 8f6a959ff..0e560e0b6 100644 --- a/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift +++ b/ownCloudAppShared/User Interface/Theme/NSObject+ThemeApplication.swift @@ -83,7 +83,7 @@ public extension NSObject { func applyThemeCollection(_ collection: ThemeCollection, itemStyle: ThemeItemStyle = .defaultForItem, itemState: ThemeItemState = .normal, cellState: UICellConfigurationState? = nil) { let css = collection.css - if let button = self as? UIButton, (self as? ThemeButton) == nil { + if let button = self as? UIButton, !(self is ThemeButton) { button.apply(css: css, properties: [.stroke]) } diff --git a/ownCloudAppShared/User Interface/Theme/ThemeCollection.swift b/ownCloudAppShared/User Interface/Theme/ThemeCollection.swift index 3cf6a354c..d61680635 100644 --- a/ownCloudAppShared/User Interface/Theme/ThemeCollection.swift +++ b/ownCloudAppShared/User Interface/Theme/ThemeCollection.swift @@ -206,7 +206,7 @@ public class ThemeCollection : NSObject { return colorPairs } - init(darkBrandColor darkColor: UIColor, lightBrandColor lightColor: UIColor, style: ThemeCollectionStyle = .dark, customColors: NSDictionary? = nil, genericColors: NSDictionary? = nil, interfaceStyles: NSDictionary? = nil) { + init(darkBrandColor inDarkColor: UIColor, lightBrandColor inLightColor: UIColor, style: ThemeCollectionStyle = .dark, interfaceStyles: NSDictionary? = nil, useSystemColors: Bool = false, systemTintColor: UIColor? = nil) { var logoFillColor : UIColor? self.css = ThemeCSS() @@ -218,8 +218,12 @@ public class ThemeCollection : NSObject { var statusBarStyle : UIStatusBarStyle var barStyle : UIBarStyle - let darkBrandColor = darkColor - let lightBrandColor = lightColor + let styleTraitCollection = UITraitCollection(userInterfaceStyle: style.userInterfaceStyle) + + let darkBrandColor = inDarkColor.resolvedColor(with: styleTraitCollection) + let lightBrandColor = inLightColor.resolvedColor(with: styleTraitCollection) + + let resolvedSystemTintColor: UIColor = systemTintColor ?? .tintColor.resolvedColor(with: styleTraitCollection) /* Cells: @@ -241,10 +245,8 @@ public class ThemeCollection : NSObject { - confirm - .. */ - var lightBrandSet = ThemeColorSet.from(backgroundColor: lightColor, tintColor: darkColor, for: style.userInterfaceStyle) - let darkBrandSet = ThemeColorSet.from(backgroundColor: darkColor, tintColor: lightColor, for: style.userInterfaceStyle) - - let styleTraitCollection = UITraitCollection(userInterfaceStyle: interfaceStyle) + var lightBrandSet = ThemeColorSet.from(backgroundColor: lightBrandColor, tintColor: darkBrandColor, for: style.userInterfaceStyle) + let darkBrandSet = ThemeColorSet.from(backgroundColor: darkBrandColor, tintColor: lightBrandColor, for: style.userInterfaceStyle) var cellSet: ThemeColorSet var groupedCellSet: ThemeColorSet @@ -282,7 +284,7 @@ public class ThemeCollection : NSObject { var modalBackgroundColor: UIColor let lightBrandColors = ThemeColorCollection( - backgroundColor: lightColor, + backgroundColor: lightBrandColor, tintColor: UIColor.white, labelColor: UIColor.white, secondaryLabelColor: UIColor.lightGray, @@ -321,25 +323,25 @@ public class ThemeCollection : NSObject { navigationBarSet = darkBrandSet toolbarSet = darkBrandSet - cellSet = ThemeColorSet.from(backgroundColor: UIColor(hex: 0), tintColor: lightColor, for: interfaceStyle) + cellSet = ThemeColorSet.from(backgroundColor: UIColor(hex: 0), tintColor: lightBrandColor, for: interfaceStyle) cellStateSet = ThemeColorStateSet.from(colorSet: cellSet, for: interfaceStyle) - collectionBackgroundColor = darkColor.darker(0.1) + collectionBackgroundColor = darkBrandColor.darker(0.1) - groupedCellSet = ThemeColorSet.from(backgroundColor: darkColor, tintColor: lightColor, for: interfaceStyle) + groupedCellSet = ThemeColorSet.from(backgroundColor: darkBrandColor, tintColor: lightBrandColor, for: interfaceStyle) groupedCellStateSet = ThemeColorStateSet.from(colorSet: groupedCellSet, for: interfaceStyle) - groupedCollectionBackgroundColor = navigationBarSet.backgroundColor.darker(0.3) + groupedCollectionBackgroundColor = useSystemColors ? .systemGroupedBackground.resolvedColor(with: styleTraitCollection) : navigationBarSet.backgroundColor.darker(0.3) contentNavigationBarSet = cellSet contentToolbarSet = cellSet sidebarCellStateSet = ThemeColorStateSet.from(colorSet: darkBrandSet, for: interfaceStyle) - sidebarCellStateSet.selected.backgroundColor = sidebarCellStateSet.regular.labelColor - sidebarCellStateSet.selected.labelColor = sidebarCellStateSet.regular.backgroundColor - sidebarCellStateSet.selected.iconColor = sidebarCellStateSet.regular.backgroundColor + sidebarCellStateSet.selected.backgroundColor = useSystemColors ? resolvedSystemTintColor : sidebarCellStateSet.regular.labelColor + sidebarCellStateSet.selected.labelColor = useSystemColors ? .white : sidebarCellStateSet.regular.backgroundColor + sidebarCellStateSet.selected.iconColor = sidebarCellStateSet.selected.labelColor sidebarLogoIconColor = .white sidebarLogoLabel = .white - iconSymbolColor = lightColor + iconSymbolColor = lightBrandColor separatorColor = .darkGray @@ -348,7 +350,7 @@ public class ThemeCollection : NSObject { groupedSectionHeaderColor = .lightGray groupedSectionFooterColor = .lightGray - moreHeaderBackgroundColor = darkColor.lighter(0.05) + moreHeaderBackgroundColor = darkBrandColor.lighter(0.05) modalBackgroundColor = darkBrandColor @@ -376,10 +378,10 @@ public class ThemeCollection : NSObject { sidebarAccountCellSet = ThemeColorSet.from(backgroundColor: .white, tintColor: .white, for: interfaceStyle) accountCellSet = sidebarAccountCellSet - navigationBarSet = ThemeColorSet.from(backgroundColor: .systemBackground.resolvedColor(with: styleTraitCollection), tintColor: lightColor, for: interfaceStyle) + navigationBarSet = ThemeColorSet.from(backgroundColor: .systemBackground.resolvedColor(with: styleTraitCollection), tintColor: lightBrandColor, for: interfaceStyle) toolbarSet = navigationBarSet - cellSet = ThemeColorSet.from(backgroundColor: .systemBackground.resolvedColor(with: styleTraitCollection), tintColor: lightColor, for: interfaceStyle) + cellSet = ThemeColorSet.from(backgroundColor: .systemBackground.resolvedColor(with: styleTraitCollection), tintColor: lightBrandColor, for: interfaceStyle) cellStateSet = ThemeColorStateSet.from(colorSet: cellSet, for: interfaceStyle) collectionBackgroundColor = cellSet.backgroundColor @@ -391,14 +393,14 @@ public class ThemeCollection : NSObject { contentToolbarSet = cellSet sidebarCellStateSet = ThemeColorStateSet.from(colorSet: cellSet, for: interfaceStyle) - sidebarCellStateSet.regular.backgroundColor = .secondarySystemBackground.resolvedColor(with: styleTraitCollection) + sidebarCellStateSet.regular.backgroundColor = .secondarySystemBackground.resolvedColor(with: styleTraitCollection) sidebarCellStateSet.selected.labelColor = .white sidebarCellStateSet.selected.iconColor = .white - sidebarCellStateSet.selected.backgroundColor = darkBrandColor + sidebarCellStateSet.selected.backgroundColor = useSystemColors ? resolvedSystemTintColor : darkBrandColor sidebarLogoIconColor = darkBrandColor sidebarLogoLabel = darkBrandColor - iconSymbolColor = darkColor + iconSymbolColor = darkBrandColor sectionHeaderColor = .label.resolvedColor(with: styleTraitCollection) sectionFooterColor = .secondaryLabel.resolvedColor(with: styleTraitCollection) @@ -462,6 +464,9 @@ public class ThemeCollection : NSObject { ThemeCSSRecord(selectors: [.navigationBar, .label], property: .stroke, value: navigationBarSet.labelColor), ThemeCSSRecord(selectors: [.navigationBar], property: .fill, value: navigationBarSet.backgroundColor), + ThemeCSSRecord(selectors: [.navigationBar, .popupButton, .icon],property: .stroke, value: navigationBarSet.tintColor), + ThemeCSSRecord(selectors: [.navigationBar, .popupButton, .icon],property: .fill, value: UIColor(white: 0.5, alpha: 0.3)), + // - Toolbar ThemeCSSRecord(selectors: [.toolbar], property: .stroke, value: toolbarSet.tintColor), ThemeCSSRecord(selectors: [.toolbar], property: .fill, value: toolbarSet.backgroundColor), @@ -528,6 +533,9 @@ public class ThemeCollection : NSObject { ThemeCSSRecord(selectors: [.insetGrouped, .table, .cell], property: .fill, value: groupedCellStateSet.regular.backgroundColor), ThemeCSSRecord(selectors: [.insetGrouped, .table, .highlighted, .cell], property: .fill, value: groupedCellStateSet.highlighted.backgroundColor), + ThemeCSSRecord(selectors: [.insetGrouped, .table, .sectionHeader], property: .stroke, value: groupedSectionHeaderColor), + ThemeCSSRecord(selectors: [.insetGrouped, .table, .sectionFooter], property: .stroke, value: groupedSectionFooterColor), + ThemeCSSRecord(selectors: [.table, .sectionHeader], property: .stroke, value: sectionHeaderColor), ThemeCSSRecord(selectors: [.table, .sectionFooter], property: .stroke, value: sectionFooterColor), @@ -671,8 +679,8 @@ public class ThemeCollection : NSObject { ThemeCSSRecord(selectors: [.more, .favorite, .disabled], property: .stroke, value: favoriteDisabledColor), // - TVG icon colors - ThemeCSSRecord(selectors: [.vectorImage, .folderColor], property: .fill, value: iconSymbolColor), - ThemeCSSRecord(selectors: [.vectorImage, .fileColor], property: .fill, value: iconSymbolColor), + // ThemeCSSRecord(selectors: [.vectorImage, .folderColor], property: .fill, value: iconSymbolColor), // make TVG files use default colors extracted from original SVG files + // ThemeCSSRecord(selectors: [.vectorImage, .fileColor], property: .fill, value: iconSymbolColor), // make TVG files use default colors extracted from original SVG files ThemeCSSRecord(selectors: [.vectorImage, .logoColor], property: .fill, value: logoFillColor ?? UIColor.white), ThemeCSSRecord(selectors: [.vectorImage, .iconColor], property: .fill, value: iconSymbolColor), ThemeCSSRecord(selectors: [.vectorImage, .symbolColor], property: .fill, value: iconSymbolColor), @@ -686,6 +694,23 @@ public class ThemeCollection : NSObject { ThemeCSSRecord(selectors: [.welcome, .message, .button], property: .fill, value: darkBrandColor), ThemeCSSRecord(selectors: [.welcome], property: .statusBarStyle, value: UIStatusBarStyle.lightContent), + // Account Setup + ThemeCSSRecord(selectors: [.accountSetup, .message, .title], property: .stroke, value: UIColor.white), + ThemeCSSRecord(selectors: [.accountSetup, .header, .title], property: .stroke, value: UIColor.white), + ThemeCSSRecord(selectors: [.accountSetup, .welcome, .icon], property: .fill, value: darkBrandColor), + ThemeCSSRecord(selectors: [.accountSetup, .step, .title], property: .stroke, value: primaryLabelColor), + ThemeCSSRecord(selectors: [.accountSetup, .step, .message], property: .stroke, value: secondaryLabelColor), + ThemeCSSRecord(selectors: [.accountSetup, .step, .background], property: .fill, value: collectionBackgroundColor), + ThemeCSSRecord(selectors: [.accountSetup, .step, .button, .filled], property: .stroke, value: UIColor.white), + ThemeCSSRecord(selectors: [.accountSetup, .step, .button, .filled], property: .fill, value: lightBrandColor), + ThemeCSSRecord(selectors: [.accountSetup, .help, .subtitle], property: .stroke, value: UIColor.lightGray), + ThemeCSSRecord(selectors: [.accountSetup, .help, .button], property: .stroke, value: lightBrandColor), + ThemeCSSRecord(selectors: [.accountSetup, .help, .button], property: .fill, value: UIColor.clear), + ThemeCSSRecord(selectors: [.accountSetup], property: .fill, value: darkBrandColor), + ThemeCSSRecord(selectors: [.accountSetup], property: .statusBarStyle, value: UIStatusBarStyle.lightContent), + + ThemeCSSRecord(selectors: [.certificateSummary], property: .fill, value: UIColor.clear), + // Side Bar // - Interface Style ThemeCSSRecord(selectors: [.sidebar], property: .style, value: UIUserInterfaceStyle.light), @@ -714,14 +739,14 @@ public class ThemeCollection : NSObject { ThemeCSSRecord(selectors: [.sidebar, .account, .disconnect], property: .fill, value: sidebarAccountCellSet.labelColor), // - Navigation Bar - ThemeCSSRecord(selectors: [.sidebar, .navigationBar], property: .stroke, value: lightColor), + ThemeCSSRecord(selectors: [.sidebar, .navigationBar], property: .stroke, value: lightBrandColor), ThemeCSSRecord(selectors: [.sidebar, .navigationBar], property: .fill, value: nil), ThemeCSSRecord(selectors: [.sidebar, .navigationBar, .logo], property: .stroke, value: sidebarLogoIconColor), ThemeCSSRecord(selectors: [.sidebar, .navigationBar, .logo, .label],property: .stroke, value: sidebarLogoLabel), // - Toolbar ThemeCSSRecord(selectors: [.sidebar, .toolbar], property: .fill, value: sidebarCellStateSet.regular.backgroundColor), - ThemeCSSRecord(selectors: [.sidebar, .toolbar], property: .stroke, value: lightColor), + ThemeCSSRecord(selectors: [.sidebar, .toolbar], property: .stroke, value: lightBrandColor), // Content Area ThemeCSSRecord(selectors: [.content], property: .fill, value: collectionBackgroundColor), @@ -760,7 +785,13 @@ public class ThemeCollection : NSObject { _iconColors = [:] _iconColors?["folderFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .folderColor], for: nil)?.hexString() + _iconColors?["fileFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .fileColor], for: nil)?.hexString() + _iconColors?["documentFileFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .documentFileColor], for: nil)?.hexString() + _iconColors?["presentationFileFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .presentationFileColor], for: nil)?.hexString() + _iconColors?["spreadsheetFileFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .spreadsheetFileColor], for: nil)?.hexString() + _iconColors?["pdfFileFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .pdfFileColor], for: nil)?.hexString() + _iconColors?["logoFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .logoColor], for: nil)?.hexString() _iconColors?["iconFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .iconColor], for: nil)?.hexString() _iconColors?["symbolFillColor"] = css.getColor(.fill, selectors: [.vectorImage, .symbolColor], for: nil)?.hexString() @@ -773,6 +804,10 @@ public class ThemeCollection : NSObject { extension ThemeCSSSelector { static let folderColor = ThemeCSSSelector(rawValue: "folderColor") static let fileColor = ThemeCSSSelector(rawValue: "fileColor") + static let documentFileColor = ThemeCSSSelector(rawValue: "documentFileColor") + static let presentationFileColor = ThemeCSSSelector(rawValue: "presentationFileColor") + static let spreadsheetFileColor = ThemeCSSSelector(rawValue: "spreadsheetFileColor") + static let pdfFileColor = ThemeCSSSelector(rawValue: "pdfFileColor") static let logoColor = ThemeCSSSelector(rawValue: "logoColor") static let iconColor = ThemeCSSSelector(rawValue: "iconColor") static let symbolColor = ThemeCSSSelector(rawValue: "symbolColor") diff --git a/ownCloudAppShared/User Interface/Theme/ThemeStyle+DefaultStyles.swift b/ownCloudAppShared/User Interface/Theme/ThemeStyle+DefaultStyles.swift index 4e52656af..70c12f29d 100644 --- a/ownCloudAppShared/User Interface/Theme/ThemeStyle+DefaultStyles.swift +++ b/ownCloudAppShared/User Interface/Theme/ThemeStyle+DefaultStyles.swift @@ -26,11 +26,10 @@ extension UIColor { } extension ThemeStyle { - static public var ownCloudLight : ThemeStyle { - return (ThemeStyle(styleIdentifier: "com.owncloud.light", darkStyleIdentifier: "com.owncloud.dark", localizedName: "Light".localized, lightColor: .ownCloudLightColor, darkColor: .ownCloudDarkColor, themeStyle: .light)) + static public func systemLight(with tintColor: UIColor? = nil, cssRecordStrings: [String]? = nil) -> ThemeStyle { + return (ThemeStyle(styleIdentifier: "com.owncloud.light", darkStyleIdentifier: "com.owncloud.dark", localizedName: "Light".localized, lightColor: tintColor ?? .tintColor, darkColor: .label, themeStyle: .light, useSystemColors: true, systemTintColor: tintColor, cssRecordStrings: cssRecordStrings)) } - - static public var ownCloudDark : ThemeStyle { - return (ThemeStyle(styleIdentifier: "com.owncloud.dark", localizedName: "Dark".localized, lightColor: .ownCloudLightColor, darkColor: .ownCloudDarkColor, themeStyle: .dark)) + static public func systemDark(with tintColor: UIColor? = nil, cssRecordStrings: [String]? = nil) -> ThemeStyle { + return (ThemeStyle(styleIdentifier: "com.owncloud.dark", localizedName: "Dark".localized, lightColor: tintColor ?? .tintColor, darkColor: .secondarySystemGroupedBackground, themeStyle: .dark, useSystemColors: true, systemTintColor: tintColor, cssRecordStrings: cssRecordStrings)) } } diff --git a/ownCloudAppShared/User Interface/Theme/ThemeStyle+Extensions.swift b/ownCloudAppShared/User Interface/Theme/ThemeStyle+Extensions.swift index a64fd1c12..e9b8e2876 100644 --- a/ownCloudAppShared/User Interface/Theme/ThemeStyle+Extensions.swift +++ b/ownCloudAppShared/User Interface/Theme/ThemeStyle+Extensions.swift @@ -47,7 +47,7 @@ extension ThemeStyle { Log.error("Couldn't get defaultStyle") - return ThemeStyle.ownCloudDark + return ThemeStyle.systemDark() } static public var preferredStyle : ThemeStyle { @@ -100,13 +100,11 @@ extension ThemeStyle { if self.followSystemAppearance { if ThemeStyle.userInterfaceStyle() == .dark { - if let darkStyleIdentifier = ThemeStyle.preferredStyle.darkStyleIdentifier, let style = ThemeStyle.forIdentifier(darkStyleIdentifier) { - ThemeStyle.preferredStyle = style + if let style = ThemeStyle.forIdentifier("com.owncloud.dark") { applyStyle = style } } else { - if ThemeStyle.preferredStyle.themeStyle == .dark, let style = ThemeStyle.availableStyles(for: [.light])?.first { - ThemeStyle.preferredStyle = style + if let style = ThemeStyle.forIdentifier("com.owncloud.light") { applyStyle = style } } @@ -144,7 +142,7 @@ extension ThemeStyle { } if followSystemAppearance == nil { - followSystemAppearance = false + followSystemAppearance = true } return followSystemAppearance! @@ -185,8 +183,8 @@ extension ThemeStyle { static public func registerDefaultStyles() { if !Branding.shared.setupThemeStyles() { - OCExtensionManager.shared.addExtension(ThemeStyle.ownCloudLight.themeStyleExtension(isDefault: true)) - OCExtensionManager.shared.addExtension(ThemeStyle.ownCloudDark.themeStyleExtension()) + OCExtensionManager.shared.addExtension(ThemeStyle.systemLight().themeStyleExtension(isDefault: true)) + OCExtensionManager.shared.addExtension(ThemeStyle.systemDark().themeStyleExtension()) } } diff --git a/ownCloudAppShared/User Interface/Theme/ThemeStyle.swift b/ownCloudAppShared/User Interface/Theme/ThemeStyle.swift index 67548bb2f..b156f8099 100644 --- a/ownCloudAppShared/User Interface/Theme/ThemeStyle.swift +++ b/ownCloudAppShared/User Interface/Theme/ThemeStyle.swift @@ -30,21 +30,22 @@ public class ThemeStyle : NSObject { public var darkStyleIdentifier: String? - public var customColors : NSDictionary? - public var genericColors : NSDictionary? public var styles : NSDictionary? + public var useSystemColors: Bool + public var systemTintColor: UIColor? + public var cssRecordStrings: [String]? - public init(styleIdentifier: String, darkStyleIdentifier darkIdentifier: String? = nil, localizedName name: String, lightColor lColor: UIColor, darkColor dColor: UIColor, themeStyle style: ThemeCollectionStyle = .light, customColors: NSDictionary? = nil, genericColors: NSDictionary? = nil, interfaceStyles: NSDictionary? = nil, cssRecordStrings: [String]? = nil) { + public init(styleIdentifier: String, darkStyleIdentifier darkIdentifier: String? = nil, localizedName name: String, lightColor lColor: UIColor, darkColor dColor: UIColor, themeStyle style: ThemeCollectionStyle = .light, useSystemColors: Bool = false, systemTintColor: UIColor? = nil, interfaceStyles: NSDictionary? = nil, cssRecordStrings: [String]? = nil) { self.identifier = styleIdentifier self.darkStyleIdentifier = darkIdentifier self.localizedName = name self.lightColor = lColor self.darkColor = dColor self.themeStyle = style - self.customColors = customColors - self.genericColors = genericColors + self.useSystemColors = useSystemColors + self.systemTintColor = systemTintColor self.styles = interfaceStyles self.cssRecordStrings = cssRecordStrings } @@ -107,7 +108,7 @@ public extension ThemeCSSRecord { public extension ThemeCollection { convenience init(with style: ThemeStyle) { - self.init(darkBrandColor: style.darkColor, lightBrandColor: style.lightColor, style: style.themeStyle, customColors: style.customColors, genericColors: style.genericColors, interfaceStyles: style.styles) + self.init(darkBrandColor: style.darkColor, lightBrandColor: style.lightColor, style: style.themeStyle, interfaceStyles: style.styles, useSystemColors: style.useSystemColors, systemTintColor: style.systemTintColor) if let cssRecordStrings = style.cssRecordStrings, let records = ThemeCSSRecord.from(cssRecordStrings) { css.add(records: records) diff --git a/ownCloudAppShared/User Interface/Theme/UI/ThemeNavigationController.swift b/ownCloudAppShared/User Interface/Theme/UI/ThemeNavigationController.swift index 830072b67..30b707514 100644 --- a/ownCloudAppShared/User Interface/Theme/UI/ThemeNavigationController.swift +++ b/ownCloudAppShared/User Interface/Theme/UI/ThemeNavigationController.swift @@ -42,6 +42,22 @@ open class ThemeNavigationController: UINavigationController, Themeable { return Theme.shared.activeCollection.css.getStatusBarStyle(for: self) ?? .default } + open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + if let viewController = viewControllers.last { + return viewController.supportedInterfaceOrientations + } + + return super.supportedInterfaceOrientations + } + + open override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + if let viewController = viewControllers.last { + return viewController.preferredInterfaceOrientationForPresentation + } + + return super.preferredInterfaceOrientationForPresentation + } + open override var childForStatusBarStyle: UIViewController? { return nil } diff --git a/removeExtension.sh b/removeExtension.sh new file mode 100755 index 000000000..69bf41518 --- /dev/null +++ b/removeExtension.sh @@ -0,0 +1,84 @@ +#! /bin/bash + + # Copyright (C) 2023, ownCloud GmbH. + # + # This code is covered by the GNU Public License Version 3. + # + # For distribution utilizing Apple mechanisms please see https://owncloud.org/contribute/iOS-license-exception/ + # You should have received a copy of this license along with this program. If not, see . + + VERSION="1.0.0" + + #Define output formats + BOLD="$(tput bold)" + WARN="$(tput setaf 1)" + SUCCESS="$(tput setaf 2)" + INFO="$(tput setaf 3)" + NC="$(tput sgr0)" # No Color + + usage() + { + echo "Usage: $0 \"Path to IPA\" \"Target Name\"" + echo "Version: ${VERSION}" + echo "" + exit 1 + } + + + #Check if all required parameters exist + if [ $# -lt 2 ]; then + usage + fi + + echo + echo "${BOLD}${SUCCESS}Remove Extension Tool${NC}" + echo "Version ${VERSION}" + echo + + # Extract the file name from the path +IPA_FILE=$1 + APPTEMP="apptemp" + APPPATH="$APPTEMP/ownCloud.app" + + + # Delete previous temporal app folder if exist + if [ -d "$APPTEMP" ]; then + rm -rf "$APPTEMP" + fi + + # Create temp directory + mkdir $APPTEMP + + export PATH=$PATH:/usr/libexec + + # Unzip ipa + echo "${SUCCESS}Unzipping ipa…${NC}" + echo "" + + unzip -q "$IPA_FILE" -d "$APPTEMP" || { echo "${WARN}Failed to unzip ipa file${NC}"; exit 1; } + + if [ ! -d "$APPPATH" ]; then + APPPATH="$APPTEMP/Payload/ownCloud.app" + fi + +EXTENSIONPATH="$APPPATH/PlugIns/$2.appex" + +echo "${SUCCESS}Remove $EXTENSIONPATH ${NC}" + +# Remove the extension +rm -rf "$EXTENSIONPATH" + +# Delete input IPA file +rm -rf "$IPA_FILE" + +# Generate new Payload +echo "" +echo "${SUCCESS}Packing new ipa…${NC}" +pushd "$APPTEMP" +zip -q -r "../$IPA_FILE" * +popd + +# Delete previous temporal app folder if exist +if [ -d "$APPTEMP" ]; then + rm -rf "$APPTEMP" +fi \ No newline at end of file diff --git a/tools/GenerateDocs/templates/configuration.adoc.tmpl b/tools/GenerateDocs/templates/configuration.adoc.tmpl index f0826d78f..ab9684dd5 100644 --- a/tools/GenerateDocs/templates/configuration.adoc.tmpl +++ b/tools/GenerateDocs/templates/configuration.adoc.tmpl @@ -39,6 +39,18 @@ tag::{{$categoryTag | strings.ToLower }}[] {{end}} !=== {{end}} +{{- if has $option "possibleKeys"}} +[cols="1,1"] +!=== +! Key +! Value + +{{- range $possibleKeyIndex, $possibleKey := $option.possibleKeys }} +! `{{ $possibleKey.value }}` +! {{ $possibleKey.description }} +{{end}} +!=== +{{end}} |{{- $option.status}}{{if ne $option.status "debugOnly"}} `candidate`{{end}} {{end}} {{- end}}