---------

Signed-off-by: Marino Faggiana <marino@marinofaggiana.com>
Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com>
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
Signed-off-by: Milen Pivchev <jtodorov_kostadinov@tu-sofia.bg>
Signed-off-by: Marco Ambrosini <marcoambrosini@proton.me>
Signed-off-by: Claudio Cambra <developer@claudiocambra.com>
This commit is contained in:
Marino Faggiana 2024-05-25 09:06:05 +02:00 коммит произвёл GitHub
Родитель 08d7f76e4c
Коммит ffefb25d14
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
603 изменённых файлов: 4921 добавлений и 6319 удалений

25
.github/workflows/additional-targets.yml поставляемый
Просмотреть файл

@ -11,23 +11,22 @@ on:
- master
- develop
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
name: Build and Test
runs-on: macos-latest
runs-on: macos-14
if: github.event.pull_request.draft == false
env:
PROJECT: Nextcloud.xcodeproj
DESTINATION: platform=iOS Simulator,name=iPhone 14
DESTINATION: platform=iOS Simulator,name=iPhone 15
steps:
- name: Set env var
run: echo "DEVELOPER_DIR=$(xcode-select --print-path)" >> $GITHUB_ENV
- uses: actions/checkout@v3
- name: Setup Bundler and Install Gems
run: |
gem install bundler
bundle install
bundle update
- uses: actions/checkout@v4
- name: Restore Carhage Cache
uses: actions/cache@v3
id: carthage-cache
@ -43,26 +42,26 @@ jobs:
run: wget "https://raw.githubusercontent.com/firebase/quickstart-ios/master/mock-GoogleService-Info.plist" -O GoogleService-Info.plist
- name: Build iOS Share
run: |
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcpretty
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcbeautify --quieter
env:
SCHEME: Share
- name: Build iOS File Extension
run: |
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcpretty
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcbeautify --quieter
env:
SCHEME: File Provider Extension
- name: Build iOS Notification Extension
run: |
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcpretty
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcbeautify --quieter
env:
SCHEME: Notification Service Extension
- name: Build iOS Widget
run: |
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcpretty
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcbeautify --quieter
env:
SCHEME: Widget
- name: Build iOS Widget Dashboard IntentHandler
run: |
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcpretty
xcodebuild build -project $PROJECT -scheme "$SCHEME" -destination "$DESTINATION" | xcbeautify --quieter
env:
SCHEME: WidgetDashboardIntentHandler

6
.github/workflows/lint.yml поставляемый
Просмотреть файл

@ -14,13 +14,17 @@ on:
- master
- develop
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
Lint:
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: GitHub Action for SwiftLint
uses: norio-nomura/action-swiftlint@3.2.1

72
.github/workflows/xcode.xxx поставляемый
Просмотреть файл

@ -1,72 +0,0 @@
name: Build main target
on:
push:
branches:
- master
- develop
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
branches:
- master
- develop
jobs:
build-and-test:
name: Build and Test
runs-on: macos-latest
if: github.event.pull_request.draft == false
env:
PROJECT: Nextcloud.xcodeproj
DESTINATION: platform=iOS Simulator,name=iPhone 14
SCHEME: Nextcloud
steps:
- name: Set env var
run: echo "DEVELOPER_DIR=$(xcode-select --print-path)" >> $GITHUB_ENV
- uses: actions/checkout@v3
- name: Setup Bundler and Install Gems
run: |
gem install bundler
bundle install
bundle update
- name: Restore Carhage Cache
uses: actions/cache@v3
id: carthage-cache
with:
path: Carthage
key: ${{ runner.os }}-carthage-${{ hashFiles('**/Cartfile.resolved') }}
restore-keys: |
${{ runner.os }}-carthage-
- name: Carthage
if: steps.carthage-cache.outputs.cache-hit != 'true'
run: carthage bootstrap --use-xcframeworks --platform iOS
- name: Download GoogleService-Info.plist
run: wget "https://raw.githubusercontent.com/firebase/quickstart-ios/master/mock-GoogleService-Info.plist" -O GoogleService-Info.plist
- name: Install docker
run: |
brew install colima
brew install docker
colima start
- name: Create docker test server and export enviroment variables
run: |
source ./create-docker-test-server.sh
if [ ! -f ".env-vars" ]; then
touch .env-vars
echo "export TEST_SERVER_URL=$TEST_SERVER_URL" >> .env-vars
echo "export TEST_USER=$TEST_USER" >> .env-vars
echo "export TEST_APP_PASSWORD=$TEST_APP_PASSWORD" >> .env-vars
fi
- name: Build & Test Nextcloud iOS
run: |
set -o pipefail && xcodebuild test -project $PROJECT \
-scheme "$SCHEME" \
-destination "$DESTINATION" \
-enableCodeCoverage YES \
-test-iterations 3 \
-retry-tests-on-failure \
| xcpretty
- name: Upload coverage to codecov
run: |
bundle exec slather
bash <(curl -s https://codecov.io/bash) -f ./cobertura.xml -X coveragepy -X gcov -X xcode -t ${{ secrets.CODECOV_TOKEN }}

129
.github/workflows/xcode.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,129 @@
name: Build and test main target
on:
push:
branches:
- master
- develop
pull_request:
types: [synchronize, opened, reopened, ready_for_review]
branches:
- master
- develop
env:
PROJECT: Nextcloud.xcodeproj
DESTINATION: platform=iOS Simulator,name=iPhone 15,OS=17.2
SCHEME: Nextcloud
SERVER_BRANCH: stable28
PHP_VERSION: 8.2
jobs:
build:
name: Build
runs-on: macos-13
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
- name: Restore Carhage Cache
uses: actions/cache@v3
id: carthage-cache
with:
path: Carthage
key: ${{ runner.os }}-carthage-${{ hashFiles('**/Cartfile.resolved') }}
restore-keys: |
${{ runner.os }}-carthage-
- name: Carthage
if: steps.carthage-cache.outputs.cache-hit != 'true'
run: carthage bootstrap --use-xcframeworks --platform iOS
- name: Download GoogleService-Info.plist
run: wget "https://raw.githubusercontent.com/firebase/quickstart-ios/master/mock-GoogleService-Info.plist" -O GoogleService-Info.plist
- name: Build Nextcloud iOS
run: |
set -o pipefail && \
xcodebuild build-for-testing \
-scheme "${{ env.SCHEME }}" \
-destination "${{ env.DESTINATION }}" \
-derivedDataPath "DerivedData" \
| xcbeautify --quieter
- name: Upload test build
uses: actions/upload-artifact@v4
with:
name: Nextcloud iOS
path: DerivedData/Build/Products
retention-days: 4
test:
name: Test
runs-on: macos-13
needs: [build]
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
- name: Set up php ${{ env.PHP_VERSION }}
uses: shivammathur/setup-php@8872c784b04a1420e81191df5d64fbd59d3d3033 # v2.30.0
with:
php-version: ${{ env.PHP_VERSION }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
extensions: apcu, bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3: ini-values: apc.enable_cli=on
ini-values: apc.enable_cli=on, disable_functions=
- name: Checkout server
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: true
repository: nextcloud/server
path: server
ref: ${{ env.SERVER_BRANCH }}
- name: Set up Nextcloud
run: |
mkdir server/data
./server/occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
./server/occ config:system:set hashing_default_password --value=true --type=boolean
./server/occ config:system:set auth.bruteforce.protection.enabled --value false --type bool
./server/occ config:system:set ratelimit.protection.enabled --value false --type bool
./server/occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
./server/occ config:system:set memcache.distributed --value="\\OC\\Memcache\\APCu"
./server/occ background:cron
PHP_CLI_SERVER_WORKERS=5 php -S localhost:8080 -t server/ &
- name: Download test build
uses: actions/download-artifact@v4
with:
name: Nextcloud iOS
- name: Check server status
run: curl -s --retry 5 --retry-delay 60 --retry-all-errors http://localhost:8080/status.php || true
- name: Test Nextcloud iOS
run: |
set -o pipefail && \
xcodebuild test-without-building \
-xctestrun $(find . -type f -name "*.xctestrun") \
-destination "${{ env.DESTINATION }}" \
-derivedDataPath "DerivedData" \
-test-iterations 3 \
-retry-tests-on-failure \
-resultBundlePath "TestResult.xcresult" \
| xcbeautify --quieter
- name: Upload test results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: TestResult.xcresult
path: "TestResult.xcresult"

Просмотреть файл

@ -1,3 +0,0 @@
coverage_service: cobertura_xml
xcodeproj: Nextcloud.xcodeproj
scheme: Nextcloud

Просмотреть файл

@ -44,5 +44,6 @@ excluded:
- Brand/NCBrand.swift
- iOSClient/NCGlobal.swift
- iOSClient/Utility/NCLivePhoto.swift
- DerivedData
reporter: "xcode"

Просмотреть файл

@ -26,4 +26,4 @@ import Foundation
// Database Realm
//
let databaseName = "nextcloud.realm"
let databaseSchemaVersion: UInt64 = 346
let databaseSchemaVersion: UInt64 = 347

Просмотреть файл

@ -34,6 +34,7 @@ class NCIntroViewController: UIViewController, UICollectionViewDataSource, UICol
@IBOutlet weak var pageControl: UIPageControl!
@objc weak var delegate: NCIntroViewController?
private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
private let titles = [NSLocalizedString("_intro_1_title_", comment: ""), NSLocalizedString("_intro_2_title_", comment: ""), NSLocalizedString("_intro_3_title_", comment: ""), NSLocalizedString("_intro_4_title_", comment: "")]
private let images = [UIImage(named: "intro1"), UIImage(named: "intro2"), UIImage(named: "intro3"), UIImage(named: "intro4")]
@ -113,8 +114,11 @@ class NCIntroViewController: UIViewController, UICollectionViewDataSource, UICol
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
pageControl.currentPage = 0
introCollectionView.collectionViewLayout.invalidateLayout()
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: nil) { _ in
self.pageControl?.currentPage = 0
self.introCollectionView?.collectionViewLayout.invalidateLayout()
}
}
@objc func autoScroll() {
@ -158,11 +162,11 @@ class NCIntroViewController: UIViewController, UICollectionViewDataSource, UICol
}
@IBAction func login(_ sender: Any) {
appDelegate.openLogin(viewController: navigationController, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
appDelegate.openLogin(selector: NCGlobal.shared.introLogin, openLoginWeb: false)
}
@IBAction func signup(_ sender: Any) {
appDelegate.openLogin(viewController: navigationController, selector: NCGlobal.shared.introSignup, openLoginWeb: false)
appDelegate.openLogin(selector: NCGlobal.shared.introSignup, openLoginWeb: false)
}
@IBAction func host(_ sender: Any) {

Просмотреть файл

@ -149,7 +149,7 @@ class NCBrandColor: NSObject {
@objc public let customer: UIColor = UIColor(red: 0.0 / 255.0, green: 130.0 / 255.0, blue: 201.0 / 255.0, alpha: 1.0) // BLU NC : #0082c9
@objc public var customerText: UIColor = .white
@objc public var brand: UIColor // don't touch me
@objc private var brand: UIColor // don't touch me
@objc public var brandElement: UIColor // don't touch me
@objc public var brandText: UIColor // don't touch me
@ -161,12 +161,37 @@ class NCBrandColor: NSObject {
public var themingColorElement: String = ""
public var themingColorText: String = ""
@objc public let iconImageColor: UIColor = .label
@objc public let iconImageColor2: UIColor = .secondaryLabel
@objc public let iconImageMultiColors: [UIColor] = [.secondaryLabel, .label]
@objc public let textColor: UIColor = .label
@objc public let textColor2: UIColor = .secondaryLabel
@objc public var systemMint: UIColor {
get {
return UIColor(red: 0.0 / 255.0, green: 199.0 / 255.0, blue: 190.0 / 255.0, alpha: 1.0)
}
}
@objc public var documentIconColor: UIColor {
get {
return UIColor(hex: "#49abe9")!
}
}
@objc public var spreadsheetIconColor: UIColor {
get {
return UIColor(hex: "#9abd4e")!
}
}
@objc public var presentationIconColor: UIColor {
get {
return UIColor(hex: "#f0965f")!
}
}
override init() {
brand = customer
brandElement = customer

Просмотреть файл

@ -2,10 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.it.twsweb.Crypto-Cloud</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)it.twsweb.Crypto-Cloud</string>

Просмотреть файл

@ -2,10 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.it.twsweb.Crypto-Cloud</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)it.twsweb.Crypto-Cloud</string>

Просмотреть файл

@ -2,10 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.it.twsweb.Crypto-Cloud</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)it.twsweb.Crypto-Cloud</string>

Просмотреть файл

@ -2,10 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.it.twsweb.Crypto-Cloud</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)it.twsweb.Crypto-Cloud</string>

Просмотреть файл

@ -4,11 +4,23 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.nextcloud.apps</string>
<string>group.it.twsweb.Crypto-Cloud</string>
</array>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)it.twsweb.Crypto-Cloud</string>

Просмотреть файл

@ -104,8 +104,6 @@
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
@ -176,5 +174,22 @@
</dict>
</dict>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>

Просмотреть файл

@ -32,7 +32,7 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
override func loadView() {
super.loadView()
view.backgroundColor = NCBrandColor.shared.brand
view.backgroundColor = NCBrandColor.shared.brandElement
titleError.textColor = NCBrandColor.shared.brandText
cancelButton.setTitleColor(NCBrandColor.shared.brandText, for: .normal)

Просмотреть файл

@ -93,7 +93,6 @@ class fileProviderData: NSObject {
NCManageDatabase.shared.setCapabilities(account: account)
NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: NCKeychain().getPassword(account: activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, delegate: NCNetworking.shared)
NCNetworking.shared.delegate = providerExtension as? NCNetworkingDelegate
return tableAccount.init(value: activeAccount)
}
@ -117,7 +116,6 @@ class fileProviderData: NSObject {
NCManageDatabase.shared.setCapabilities(account: account)
NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: NCKeychain().getPassword(account: activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, delegate: NCNetworking.shared)
NCNetworking.shared.delegate = providerExtension as? NCNetworkingDelegate
return tableAccount.init(value: activeAccount)
}

Просмотреть файл

@ -212,24 +212,20 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
if error == .success {
DispatchQueue.global().async {
NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: true) { _, metadatasFolder, metadatas in
NCManageDatabase.shared.convertFilesToMetadatas(files, useFirstAsMetadataFolder: true) { _, metadatas in
let predicate = NSPredicate(format: "account == %@ AND serverUrl == %@ AND status == %d", account, serverUrl, NCGlobal.shared.metadataStatusNormal)
NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
for metadata in metadatasFolder {
let serverUrl = metadata.serverUrl + "/" + metadata.fileNameView
NCManageDatabase.shared.addDirectory(e2eEncrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: serverUrl, account: metadata.account)
}
let metadatas = NCManageDatabase.shared.getAdvancedMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", fileProviderData.shared.account, serverUrl), sorted: "fileName", ascending: true)
let metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", fileProviderData.shared.account, serverUrl), sorted: "fileName", ascending: true)
completionHandler(metadatas)
}
}
} else {
let metadatas = NCManageDatabase.shared.getAdvancedMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", fileProviderData.shared.account, serverUrl), sorted: "fileName", ascending: true)
let metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", fileProviderData.shared.account, serverUrl), sorted: "fileName", ascending: true)
completionHandler(metadatas)
}
}
} else {
let metadatas = NCManageDatabase.shared.getAdvancedMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", fileProviderData.shared.account, serverUrl), sorted: "fileName", ascending: true)
let metadatas = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", fileProviderData.shared.account, serverUrl), sorted: "fileName", ascending: true)
completionHandler(metadatas)
}
}

Просмотреть файл

@ -51,7 +51,7 @@ import Alamofire
-------------------------------------------------------------------------------------------------------------------------------------------- */
class FileProviderExtension: NSFileProviderExtension, NCNetworkingDelegate {
class FileProviderExtension: NSFileProviderExtension {
var outstandingSessionTasks: [URL: URLSessionTask] = [:]
var outstandingOcIdTemp: [String: String] = [:]

Просмотреть файл

@ -1,3 +0,0 @@
source 'https://rubygems.org'
gem 'slather'
gem 'xcpretty'

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -21,7 +21,7 @@
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "NO"
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
@ -35,7 +35,7 @@
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "NO"
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
@ -49,7 +49,7 @@
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "NO"
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
@ -92,6 +92,26 @@
ReferencedContainer = "container:Nextcloud.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C04E2F1F2A17BB4D001BAD85"
BuildableName = "NextcloudIntegrationTests.xctest"
BlueprintName = "NextcloudIntegrationTests"
ReferencedContainer = "container:Nextcloud.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C0046CD92A17B98400D87C9D"
BuildableName = "NextcloudUITests.xctest"
BlueprintName = "NextcloudUITests"
ReferencedContainer = "container:Nextcloud.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction

Просмотреть файл

@ -63,7 +63,7 @@ class NCShareCell: UITableViewCell {
let fileSize = utilityFileSystem.getFileSize(filePath: (NSTemporaryDirectory() + fileName))
sizeCell?.text = utilityFileSystem.transformedSize(fileSize)
moreButton?.setImage(utility.loadImage(named: "more").image(color: .label, size: 15), for: .normal)
moreButton?.setImage(NCImageCache.images.buttonMore, for: .normal)
}
@IBAction func buttonTapped(_ sender: Any) {

Просмотреть файл

@ -44,11 +44,11 @@ extension NCShareExtension: UICollectionViewDelegate {
if kind == UICollectionView.elementKindSectionHeader {
guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionHeaderEmptyData", for: indexPath) as? NCSectionHeaderEmptyData else { return NCSectionHeaderEmptyData() }
if self.dataSourceTask?.state == .running {
header.emptyImage.image = UIImage(named: "networkInProgress")?.image(color: .gray, size: UIScreen.main.bounds.width)
header.emptyImage.image = utility.loadImage(named: "wifi", colors: [NCBrandColor.shared.brandElement])
header.emptyTitle.text = NSLocalizedString("_request_in_progress_", comment: "")
header.emptyDescription.text = ""
} else {
header.emptyImage.image = UIImage(named: "folder")?.image(color: NCBrandColor.shared.brandElement, size: UIScreen.main.bounds.width)
header.emptyImage.image = NCImageCache.images.folder
header.emptyTitle.text = NSLocalizedString("_files_no_folders_", comment: "")
header.emptyDescription.text = ""
}
@ -91,7 +91,7 @@ extension NCShareExtension: UICollectionViewDataSource {
cell.indexPath = indexPath
cell.fileUser = metadata.ownerId
cell.labelTitle.text = metadata.fileNameView
cell.labelTitle.textColor = .label
cell.labelTitle.textColor = NCBrandColor.shared.textColor
cell.imageSelect.image = nil
cell.imageStatus.image = nil

Просмотреть файл

@ -55,6 +55,8 @@ extension NCShareExtension: NCAccountRequestDelegate {
self.present(popup, animated: true)
}
func accountRequestAddAccount() { }
func accountRequestChangeAccount(account: String) {
guard let activeAccount = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) else {
cancel(with: NCShareExtensionError.noAccount)

Просмотреть файл

@ -100,14 +100,13 @@ class NCShareExtension: UIViewController {
commandViewHeightConstraint.constant = heightCommandView
createFolderView.layer.cornerRadius = 10
createFolderImage.image = utility.loadImage(named: "folder.badge.plus", color: .label)
createFolderImage.image = utility.loadImage(named: "folder.badge.plus", colors: [NCBrandColor.shared.iconImageColor])
createFolderLabel.text = NSLocalizedString("_create_folder_", comment: "")
let createFolderGesture = UITapGestureRecognizer(target: self, action: #selector(actionCreateFolder))
createFolderView.addGestureRecognizer(createFolderGesture)
uploadView.layer.cornerRadius = 10
// uploadImage.image = utility).loadImage(named: "square.and.arrow.up", color: .label)
uploadLabel.text = NSLocalizedString("_upload_", comment: "")
uploadLabel.textColor = .systemBlue
let uploadGesture = UITapGestureRecognizer(target: self, action: #selector(actionUpload))
@ -137,6 +136,7 @@ class NCShareExtension: UIViewController {
hud.indicatorView = JGProgressHUDRingIndicatorView()
if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
indicatorView.ringWidth = 1.5
indicatorView.ringColor = NCBrandColor.shared.brandElement
}
NotificationCenter.default.addObserver(self, selector: #selector(didCreateFolder(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCreateFolder), object: nil)
@ -160,10 +160,12 @@ class NCShareExtension: UIViewController {
self.filesName = fileNames
DispatchQueue.main.async { self.setCommandView() }
}
if NCKeychain().presentPasscode {
NCPasscode.shared.presentPasscode(viewController: self, delegate: self) {
NCPasscode.shared.enableTouchFaceID()
}
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
@ -383,6 +385,7 @@ extension NCShareExtension {
}
} else {
hud.indicatorView = JGProgressHUDSuccessIndicatorView()
hud.indicatorView?.tintColor = NCBrandColor.shared.brandElement
hud.textLabel.text = NSLocalizedString("_success_", comment: "")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.extensionContext?.completeRequest(returningItems: self.extensionContext?.inputItems, completionHandler: nil)

Просмотреть файл

@ -1,18 +0,0 @@
//
// EnvVars.stencil.swift
// NextcloudIntegrationTests
//
// Created by Milen on 31.05.23.
// Copyright © 2023 Marino Faggiana. All rights reserved.
//
import Foundation
/**
This is generated from the .env-vars file in the root directory. If there is an environment variable here that is needed and not filled, please look into this file.
*/
public struct EnvVars {
static let testUser = "{{ argument.TEST_USER }}"
static let testAppPassword = "{{ argument.TEST_APP_PASSWORD }}"
static let testServerUrl = "{{ argument.TEST_SERVER_URL }}"
}

Двоичные данные
Sourcery/bin/sourcery

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,37 @@
//
// BaseXCTestCase.swift
// Nextcloud
//
// Created by Milen on 20.03.24.
// Copyright © 2024 Marino Faggiana. All rights reserved.
//
import XCTest
import Foundation
import Alamofire
import NextcloudKit
@testable import Nextcloud
class BaseXCTestCase: XCTestCase {
var appToken = ""
func setupAppToken() {
let expectation = expectation(description: "Should get app token")
NextcloudKit.shared.getAppPassword(serverUrl: TestConstants.server, username: TestConstants.username, password: TestConstants.password) { token, data, error in
XCTAssertEqual(error.errorCode, 0)
XCTAssertNotNil(token)
guard let token else { return XCTFail() }
self.appToken = token
expectation.fulfill()
}
waitForExpectations(timeout: TestConstants.timeoutLong)
}
override func setUpWithError() throws {
setupAppToken()
}
}

Просмотреть файл

@ -0,0 +1,30 @@
//
// Copyright (c) 2023 Marcel Müller <marcel-mueller@gmx.de>
//
// Author Marcel Müller <marcel-mueller@gmx.de>
//
// GNU GPL version 3 or any later version
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
public class TestConstants {
static let timeoutLong: Double = 600
static let server = "http://localhost:8080"
static let username = "admin"
static let password = "admin"
static let account = "\(username) \(server)"
}

Просмотреть файл

@ -20,17 +20,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import XCTest
@testable import NextcloudKit
class BaseIntegrationXCTestCase: XCTestCase {
internal let baseUrl = EnvVars.testServerUrl
internal let user = EnvVars.testUser
internal let userId = EnvVars.testUser
internal let password = EnvVars.testAppPassword
internal lazy var account = "\(userId) \(baseUrl)"
internal var randomInt: Int {
class BaseIntegrationXCTestCase: BaseXCTestCase {
var randomInt: Int {
get {
return Int.random(in: 1000...Int.max)
}

Просмотреть файл

@ -34,21 +34,21 @@ final class FilesIntegrationTests: BaseIntegrationXCTestCase {
let expectation = expectation(description: "Should finish last callback")
let folderName = "TestFolder\(randomInt)"
let serverUrl = "\(baseUrl)/remote.php/dav/files/\(userId)"
let serverUrl = "\(TestConstants.server)/remote.php/dav/files/\(TestConstants.username)"
let serverUrlFileName = "\(serverUrl)/\(folderName)"
NextcloudKit.shared.setup(account: account, user: user, userId: userId, password: password, urlBase: baseUrl)
NextcloudKit.shared.setup(account: TestConstants.account, user: TestConstants.username, userId: TestConstants.username, password: appToken, urlBase: TestConstants.server)
// Test creating folder
NCNetworking.shared.createFolder(fileName: folderName, serverUrl: serverUrl, account: account, urlBase: baseUrl, userId: userId, withPush: true) { error in
NCNetworking.shared.createFolder(fileName: folderName, serverUrl: serverUrl, account: TestConstants.account, urlBase: TestConstants.server, userId: TestConstants.username, withPush: true, sceneIdentifier: nil) { error in
XCTAssertEqual(NKError.success.errorCode, error.errorCode)
XCTAssertEqual(NKError.success.errorDescription, error.errorDescription)
Thread.sleep(forTimeInterval: 0.2)
Thread.sleep(forTimeInterval: 1)
// Test reading folder, should exist
NCNetworking.shared.readFolder(serverUrl: serverUrlFileName, account: self.user) { account, metadataFolder, metadatas, metadatasUpdate, metadatasLocalUpdate, metadatasDelete, error in
XCTAssertEqual(self.account, account)
NCNetworking.shared.readFolder(serverUrl: serverUrlFileName, account: TestConstants.username) { account, metadataFolder, _, _, _, _ in
XCTAssertEqual(TestConstants.account, account)
XCTAssertEqual(NKError.success.errorCode, error.errorCode)
XCTAssertEqual(NKError.success.errorDescription, error.errorDescription)
XCTAssertEqual(metadataFolder?.fileName, folderName)
@ -57,7 +57,7 @@ final class FilesIntegrationTests: BaseIntegrationXCTestCase {
let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "serverUrl == %@", serverUrlFileName))
XCTAssertNotNil(directory)
Thread.sleep(forTimeInterval: 0.2)
Thread.sleep(forTimeInterval: 1)
Task {
// Test deleting folder
@ -66,13 +66,14 @@ final class FilesIntegrationTests: BaseIntegrationXCTestCase {
XCTAssertEqual(NKError.success.errorCode, error.errorCode)
XCTAssertEqual(NKError.success.errorDescription, error.errorDescription)
try await Task.sleep(for: .milliseconds(200))
try await Task.sleep(for: .seconds(1))
// Test reading folder, should NOT exist
NCNetworking.shared.readFolder(serverUrl: serverUrlFileName, account: self.user) { account, metadataFolder, metadatas, metadatasUpdate, metadatasLocalUpdate, metadatasDelete, error in
NCNetworking.shared.readFolder(serverUrl: serverUrlFileName, account: TestConstants.username) { account, metadataFolder, _, _, _, _ in
defer { expectation.fulfill() }
XCTAssertEqual(404, error.errorCode)
XCTAssertEqual(0, error.errorCode)
XCTAssertNil(metadataFolder?.fileName)
// Check Realm directory, should NOT exist
@ -83,6 +84,6 @@ final class FilesIntegrationTests: BaseIntegrationXCTestCase {
}
}
waitForExpectations(timeout: 100)
waitForExpectations(timeout: TestConstants.timeoutLong)
}
}

Просмотреть файл

@ -1,33 +0,0 @@
//
// SwiftUIView+Extensions.swift
// Nextcloud
//
// Created by Milen on 06.06.23.
// Copyright © 2023 Marino Faggiana. All rights reserved.
//
// Author Marino Faggiana <marino.faggiana@nextcloud.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import SwiftUI
extension SwiftUI.View {
func toVC() -> UIViewController {
let vc = UIHostingController (rootView: self)
vc.view.frame = UIScreen.main.bounds
return vc
}
}

Просмотреть файл

@ -1,37 +0,0 @@
//
// NextcloudSnapshotTests.swift
// NextcloudSnapshotTests
//
// Created by Milen on 06.06.23.
// Copyright © 2023 Marino Faggiana. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import XCTest
import SnapshotTesting
import SnapshotTestingHEIC
import PreviewSnapshotsTesting
import SwiftUI
@testable import Nextcloud
final class NextcloudSnapshotTests: XCTestCase {
func test_HUDView() {
HUDView_Previews.snapshots.assertSnapshots(as: .imageHEIC)
}
func test_CapalitiesView() {
NCCapabilitiesView_Previews.snapshots.assertSnapshots(as: .imageHEIC)
}
}

Просмотреть файл

@ -21,9 +21,7 @@
import XCTest
class BaseUIXCTestCase: XCTestCase {
let timeoutSeconds: Double = 100
class BaseUIXCTestCase: BaseXCTestCase {
override final class var runsForEachTargetApplicationUIConfiguration: Bool {
false
}
@ -31,13 +29,13 @@ class BaseUIXCTestCase: XCTestCase {
internal func waitForEnabled(object: Any?) {
let predicate = NSPredicate(format: "enabled == true")
expectation(for: predicate, evaluatedWith: object, handler: nil)
waitForExpectations(timeout: timeoutSeconds, handler: nil)
waitForExpectations(timeout: TestConstants.timeoutLong, handler: nil)
}
internal func waitForHittable(object: Any?) {
let predicate = NSPredicate(format: "hittable == true")
expectation(for: predicate, evaluatedWith: object, handler: nil)
waitForExpectations(timeout: timeoutSeconds, handler: nil)
waitForExpectations(timeout: TestConstants.timeoutLong, handler: nil)
}
internal func waitForEnabledAndHittable(object: Any?) {

Просмотреть файл

@ -20,14 +20,10 @@
//
import XCTest
import NextcloudKit
@testable import Nextcloud
final class LoginUITests: BaseUIXCTestCase {
private let baseUrl = EnvVars.testServerUrl
private let user = EnvVars.testUser
private let userId = EnvVars.testUser
private let password = EnvVars.testAppPassword
private lazy var account = "\(userId) \(baseUrl)"
let app = XCUIApplication()
override func setUp() {
@ -38,40 +34,42 @@ final class LoginUITests: BaseUIXCTestCase {
app.launch()
let loginButton = app.buttons["Log in"]
XCTAssert(loginButton.waitForExistence(timeout: timeoutSeconds))
XCTAssert(loginButton.waitForExistence(timeout: TestConstants.timeoutLong))
loginButton.tap()
let serverAddressHttpsTextField = app.textFields["Server address https:// …"]
serverAddressHttpsTextField.tap()
serverAddressHttpsTextField.typeText(baseUrl)
let button = app.windows.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .button).element(boundBy: 0)
serverAddressHttpsTextField.typeText(TestConstants.server)
let button = app.children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .button).element(boundBy: 0)
button.tap()
let webViewsQuery = app.webViews.webViews.webViews
let loginButton2 = webViewsQuery/*@START_MENU_TOKEN@*/.buttons["Log in"]/*[[".otherElements.matching(identifier: \"Nextcloud\")",".otherElements[\"main\"].buttons[\"Log in\"]",".buttons[\"Log in\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/
XCTAssert(loginButton2.waitForExistence(timeout: timeoutSeconds))
XCTAssert(loginButton2.waitForExistence(timeout: TestConstants.timeoutLong))
waitForEnabledAndHittable(object: loginButton2)
loginButton2.tap()
let element = webViewsQuery/*@START_MENU_TOKEN@*/.otherElements["main"]/*[[".otherElements[\"Login Nextcloud\"].otherElements[\"main\"]",".otherElements[\"main\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.children(matching: .other).element(boundBy: 1)
let usernameTextField = element.children(matching: .other).element(boundBy: 2).children(matching: .textField).element
XCTAssert(usernameTextField.waitForExistence(timeout: timeoutSeconds))
let usernameTextField = webViewsQuery.textFields["Login with username or email"]
XCTAssert(usernameTextField.waitForExistence(timeout: TestConstants.timeoutLong))
usernameTextField.tap()
usernameTextField.typeText(user)
let passwordTextField = element.children(matching: .other).element(boundBy: 4).children(matching: .secureTextField).element
usernameTextField.typeText(TestConstants.username)
let passwordTextField = webViewsQuery/*@START_MENU_TOKEN@*/.secureTextFields["Password"]/*[[".otherElements[\"Login Nextcloud\"]",".otherElements[\"main\"].secureTextFields[\"Password\"]",".secureTextFields[\"Password\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/
XCTAssert(passwordTextField.waitForExistence(timeout: TestConstants.timeoutLong))
passwordTextField.tap()
passwordTextField.typeText(password)
passwordTextField.typeText(TestConstants.username)
let loginButton3 = webViewsQuery/*@START_MENU_TOKEN@*/.buttons["Log in"]/*[[".otherElements[\"Login Nextcloud\"]",".otherElements[\"main\"].buttons[\"Log in\"]",".buttons[\"Log in\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/
XCTAssert(loginButton3.waitForExistence(timeout: timeoutSeconds))
XCTAssert(loginButton3.waitForExistence(timeout: TestConstants.timeoutLong))
loginButton3.tap()
let grantAccessButton = webViewsQuery/*@START_MENU_TOKEN@*/.buttons["Grant access"]/*[[".otherElements.matching(identifier: \"Nextcloud\")",".otherElements[\"main\"].buttons[\"Grant access\"]",".buttons[\"Grant access\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/
XCTAssert(grantAccessButton.waitForExistence(timeout: timeoutSeconds))
XCTAssert(grantAccessButton.waitForExistence(timeout: TestConstants.timeoutLong))
waitForEnabledAndHittable(object: grantAccessButton)
grantAccessButton.tap()
// Check if we are in the home screen
XCTAssert(app.navigationBars["Nextcloud"].waitForExistence(timeout: timeoutSeconds))
XCTAssert(app.tabBars["Tab Bar"].waitForExistence(timeout: timeoutSeconds))
XCTAssert(app.navigationBars["Nextcloud"].waitForExistence(timeout: TestConstants.timeoutLong))
XCTAssert(app.tabBars["Tab Bar"].waitForExistence(timeout: TestConstants.timeoutLong))
}
}

Просмотреть файл

@ -65,13 +65,12 @@ let dashboardDatasTest: [DashboardData] = [
]
func getDashboardItems(displaySize: CGSize, withButton: Bool) -> Int {
if withButton {
let height = Int((displaySize.height - 85) / 50)
return height
let items = Int((displaySize.height - 90) / 55)
return items
} else {
let height = Int((displaySize.height - 60) / 50)
return height
let items = Int((displaySize.height - 50) / 55)
return items
}
}
@ -164,7 +163,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis
if let fileName = tableDashboard?.iconClass {
let fileNamePath: String = utilityFileSystem.directoryUserData + "/" + fileName + ".png"
if let image = UIImage(contentsOfFile: fileNamePath) {
imagetmp = image.withTintColor(.label, renderingMode: .alwaysOriginal)
imagetmp = image.withTintColor(NCBrandColor.shared.iconImageColor, renderingMode: .alwaysOriginal)
}
}
let titleImage = imagetmp
@ -222,7 +221,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis
let path = (urlComponents.path as NSString)
let colorString = ((path.lastPathComponent) as NSString).deletingPathExtension
imageColor = UIColor(hex: colorString)
icon = UIImage(systemName: "circle.fill")!
icon = utility.loadImage(named: "circle.fill")
} else if let fileName = iconFileName {
let fileNamePath: String = utilityFileSystem.directoryUserData + "/" + fileName + ".png"
if FileManager().fileExists(atPath: fileNamePath), let image = UIImage(contentsOfFile: fileNamePath) {

Просмотреть файл

@ -37,6 +37,7 @@ struct DashboardWidgetView: View {
Image(systemName: "checkmark")
.resizable()
.scaledToFit()
.font(Font.system(.body).weight(.light))
.frame(width: 50, height: 50)
Text(NSLocalizedString("_no_items_", comment: ""))
.font(.system(size: 25))
@ -48,9 +49,7 @@ struct DashboardWidgetView: View {
}
ZStack(alignment: .topLeading) {
HStack {
Image(uiImage: entry.titleImage)
.renderingMode(.template)
.resizable()
@ -143,11 +142,13 @@ struct DashboardWidgetView: View {
.padding(.leading, 10)
.frame(height: 50)
}
if element != entry.datas.last {
Divider()
.padding(.leading, 54)
}
}
}
}
.padding(.top, 35)
.redacted(reason: entry.isPlaceholder ? .placeholder : [])
}
@ -156,7 +157,7 @@ struct DashboardWidgetView: View {
HStack(spacing: 10) {
let brandColor = Color(NCBrandColor.shared.brand)
let brandColor = Color(NCBrandColor.shared.brandElement)
let brandTextColor = Color(NCBrandColor.shared.brandText)
ForEach(buttons, id: \.index) { element in
@ -181,12 +182,13 @@ struct DashboardWidgetView: View {
.resizable()
.scaledToFit()
.frame(width: 15, height: 15)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.font(Font.system(.body).weight(.light))
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
Text(entry.footerText)
.font(.caption2)
.lineLimit(1)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
}
.padding(.horizontal, 15.0)
.frame(maxWidth: geo.size.width, maxHeight: geo.size.height - 2, alignment: .bottomTrailing)

Просмотреть файл

@ -43,6 +43,7 @@ struct FilesData: Identifiable, Hashable {
var title: String
var subTitle: String
var url: URL
var useTypeIconFile: Bool = false
}
let filesDatasTest: [FilesData] = [
@ -79,9 +80,8 @@ func getTitleFilesWidget(account: tableAccount?) -> String {
}
func getFilesItems(displaySize: CGSize) -> Int {
let height = Int((displaySize.height - 100) / 50)
return height
let items = Int((displaySize.height - 90) / 55)
return items
}
func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySize: CGSize, completion: @escaping (_ entry: FilesDataEntry) -> Void) {
@ -107,24 +107,6 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
return completion(FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: "", url: "", tile: getTitleFilesWidget(account: nil), footerImage: "xmark.icloud", footerText: NSLocalizedString("_no_active_account_", value: "No account found", comment: "")))
}
@Sendable func isLive(file: NKFile, files: [NKFile]) -> Bool {
let ext = (file.fileName as NSString).pathExtension.lowercased()
if ext != "mov" { return false }
let fileName = (file.fileName as NSString).deletingPathExtension.lowercased()
let fileNameViewMOV = fileName + ".mov"
let fileNameViewJPG = fileName + ".jpg"
let fileNameViewHEIC = fileName + ".heic"
let results = files.filter({ $0.fileName.lowercased() == fileNameViewJPG.lowercased() || $0.fileName.lowercased() == fileNameViewHEIC.lowercased() || $0.fileName.lowercased() == fileNameViewMOV.lowercased() })
if results.count == 2 {
return true
} else {
return false
}
}
// NETWORKING
let password = NCKeychain().getPassword(account: account.account)
NextcloudKit.shared.setup(
@ -181,7 +163,7 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
</d:order>
</d:orderby>
<d:limit>
<d:nresults>50</d:nresults>
<d:nresults>25</d:nresults>
</d:limit>
</d:basicsearch>
</d:searchrequest>
@ -207,18 +189,20 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start \(NCBrandOptions.shared.brand) widget session with level \(levelLog) " + versionNextcloudiOS)
}
let options = NKRequestOptions(timeout: 90, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
let options = NKRequestOptions(timeout: 30, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
NextcloudKit.shared.searchBodyRequest(serverUrl: account.urlBase, requestBody: requestBody, showHiddenFiles: NCKeychain().showHiddenFiles, options: options) { _, files, data, error in
Task {
var datas: [FilesData] = []
var imageRecent = UIImage(named: "file")!
let title = getTitleFilesWidget(account: account)
let files = files.sorted(by: { ($0.date as Date) > ($1.date as Date) })
for file in files {
var image: UIImage?
var useTypeIconFile = false
guard !file.directory else { continue }
guard !isLive(file: file, files: files) else { continue }
if file.directory || (!file.livePhotoFile.isEmpty && file.classFile == NKCommon.TypeClassFile.video.rawValue) {
continue
}
// SUBTITLE
let subTitle = utility.dateDiff(file.date as Date) + " · " + utilityFileSystem.transformedSize(file.size)
@ -232,27 +216,28 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
guard let url = URL(string: urlString) else { continue }
// IMAGE
if !file.iconName.isEmpty {
imageRecent = UIImage(named: file.iconName)!
}
if let image = utility.createFilePreviewImage(ocId: file.ocId, etag: file.etag, fileNameView: file.fileName, classFile: file.classFile, status: 0, createPreviewMedia: false) {
imageRecent = image
if let result = utility.createFilePreviewImage(ocId: file.ocId, etag: file.etag, fileNameView: file.fileName, classFile: file.classFile, status: 0, createPreviewMedia: false) {
image = result
} else if file.hasPreview {
let fileNamePathOrFileId = utilityFileSystem.getFileNamePath(file.fileName, serverUrl: file.serverUrl, urlBase: file.urlBase, userId: file.userId)
let fileNamePreviewLocalPath = utilityFileSystem.getDirectoryProviderStoragePreviewOcId(file.ocId, etag: file.etag)
let fileNameIconLocalPath = utilityFileSystem.getDirectoryProviderStorageIconOcId(file.ocId, etag: file.etag)
let sizePreview = NCUtility().getSizePreview(width: Int(file.width), height: Int(file.height))
let (_, _, imageIcon, _, _, _) = await NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId, fileNamePreviewLocalPath: fileNamePreviewLocalPath, widthPreview: Int(sizePreview.width), heightPreview: Int(sizePreview.height), fileNameIconLocalPath: fileNameIconLocalPath, sizeIcon: NCGlobal.shared.sizeIcon, options: options)
if let image = imageIcon {
imageRecent = image
if let result = imageIcon {
image = result
}
}
if image == nil {
image = utility.loadImage(named: file.iconName, useTypeIconFile: true)
useTypeIconFile = true
}
let isDirectoryE2EE = utilityFileSystem.isDirectoryE2EE(file: file)
let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
// DATA
let data = FilesData(id: metadata.ocId, image: imageRecent, title: metadata.fileNameView, subTitle: subTitle, url: url)
let data = FilesData(id: metadata.ocId, image: image!, title: metadata.fileNameView, subTitle: subTitle, url: url, useTypeIconFile: useTypeIconFile)
datas.append(data)
if datas.count == filesItems { break}
}

Просмотреть файл

@ -38,12 +38,12 @@ struct FilesWidgetView: View {
let linkActionVoiceMemo: URL = URL(string: NCGlobal.shared.widgetActionVoiceMemo + parameterLink) != nil ? URL(string: NCGlobal.shared.widgetActionVoiceMemo + parameterLink)! : URL(string: NCGlobal.shared.widgetActionVoiceMemo)!
GeometryReader { geo in
if entry.isEmpty {
VStack(alignment: .center) {
Image(systemName: "checkmark")
.resizable()
.scaledToFit()
.font(Font.system(.body).weight(.light))
.frame(width: 50, height: 50)
Text(NSLocalizedString("_no_items_", comment: ""))
.font(.system(size: 25))
@ -57,7 +57,6 @@ struct FilesWidgetView: View {
ZStack(alignment: .topLeading) {
HStack {
Text(entry.tile)
.font(.system(size: 12))
.fontWeight(.bold)
@ -70,48 +69,51 @@ struct FilesWidgetView: View {
if !entry.isEmpty {
VStack(alignment: .leading) {
VStack(spacing: 0) {
ForEach(entry.datas, id: \.id) { element in
Link(destination: element.url) {
HStack {
if element.useTypeIconFile {
Image(uiImage: element.image)
.resizable()
.renderingMode(.template)
.foregroundColor(Color(NCBrandColor.shared.iconImageColor2))
.scaledToFit()
.frame(width: 35, height: 35)
} else {
Image(uiImage: element.image)
.resizable()
.scaledToFill()
.frame(width: 35, height: 35)
.clipped()
.cornerRadius(5)
}
VStack(alignment: .leading, spacing: 2) {
Text(element.title)
.font(.system(size: 12))
.fontWeight(.regular)
Text(element.subTitle)
.font(.system(size: CGFloat(10)))
.foregroundColor(Color(.systemGray))
.foregroundColor(Color(NCBrandColor.shared.iconImageColor2))
}
Spacer()
}
.padding(.leading, 10)
.frame(height: 50)
}
if element != entry.datas.last {
Divider()
.padding(.leading, 54)
}
}
}
}
.padding(.top, 30)
.redacted(reason: entry.isPlaceholder ? .placeholder : [])
}
HStack(spacing: 0) {
let sizeButton: CGFloat = 40
Link(destination: entry.isPlaceholder ? linkNoAction : linkActionUploadAsset, label: {
@ -120,7 +122,7 @@ struct FilesWidgetView: View {
.renderingMode(.template)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandText))
.padding(11)
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
.clipShape(Circle())
.scaledToFit()
.frame(width: geo.size.width / 4, height: sizeButton)
@ -132,9 +134,10 @@ struct FilesWidgetView: View {
.renderingMode(.template)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandText))
.padding(11)
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
.clipShape(Circle())
.scaledToFit()
.font(Font.system(.body).weight(.light))
.frame(width: geo.size.width / 4, height: sizeButton)
})
@ -144,7 +147,7 @@ struct FilesWidgetView: View {
.renderingMode(.template)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandText))
.padding(11)
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
.clipShape(Circle())
.scaledToFit()
.frame(width: geo.size.width / 4, height: sizeButton)
@ -156,27 +159,27 @@ struct FilesWidgetView: View {
.renderingMode(.template)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandText))
.padding(11)
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
.clipShape(Circle())
.scaledToFit()
.frame(width: geo.size.width / 4, height: sizeButton)
})
}
.frame(width: geo.size.width, height: geo.size.height - 25, alignment: .bottomTrailing)
.frame(width: geo.size.width, height: geo.size.height - 22, alignment: .bottomTrailing)
.redacted(reason: entry.isPlaceholder ? .placeholder : [])
HStack {
Image(systemName: entry.footerImage)
.resizable()
.scaledToFit()
.frame(width: 15, height: 15)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.font(Font.system(.body).weight(.light))
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
Text(entry.footerText)
.font(.caption2)
.lineLimit(1)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
}
.padding(.horizontal, 15.0)
.frame(maxWidth: geo.size.width, maxHeight: geo.size.height - 2, alignment: .bottomTrailing)

Просмотреть файл

@ -39,7 +39,7 @@ struct LockscreenWidgetView: View {
label: {},
currentValueLabel: {
Image(systemName: "xmark.icloud")
.font(.system(size: 25.0))
.font(.system(size: 25.0).weight(.light))
}
)
.gaugeStyle(.accessoryCircularCapacity)
@ -66,17 +66,17 @@ struct LockscreenWidgetView: View {
.renderingMode(.template)
.resizable()
.scaledToFill()
.foregroundColor(.gray)
.foregroundColor(Color(NCBrandColor.shared.textColor2))
.frame(width: 11, height: 11)
Text(NSLocalizedString("_recent_activity_", comment: ""))
.font(.system(size: 11))
.fontWeight(.heavy)
.foregroundColor(.gray)
.foregroundColor(Color(NCBrandColor.shared.textColor2))
}
if entry.error {
VStack(spacing: 1) {
Image(systemName: "xmark.icloud")
.font(.system(size: 25.0))
.font(Font.system(size: 25.0).weight(.light))
.frame(maxWidth: .infinity, alignment: .center)
}.padding(8)
} else {

Просмотреть файл

@ -51,7 +51,7 @@ struct ToolbarWidgetView: View {
.renderingMode(.template)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandText))
.padding()
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
.clipShape(Circle())
.scaledToFit()
.frame(width: geo.size.width / 4, height: sizeButton)
@ -61,9 +61,10 @@ struct ToolbarWidgetView: View {
Image(systemName: "doc.text.viewfinder")
.resizable()
.renderingMode(.template)
.font(Font.system(.body).weight(.light))
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandText))
.padding()
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
.clipShape(Circle())
.scaledToFit()
.frame(width: geo.size.width / 4, height: sizeButton)
@ -75,7 +76,7 @@ struct ToolbarWidgetView: View {
.renderingMode(.template)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandText))
.padding()
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
.clipShape(Circle())
.scaledToFit()
.frame(width: geo.size.width / 4, height: sizeButton)
@ -86,7 +87,7 @@ struct ToolbarWidgetView: View {
.resizable()
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandText))
.padding()
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
.clipShape(Circle())
.scaledToFit()
.frame(width: geo.size.width / 4, height: sizeButton)
@ -96,17 +97,17 @@ struct ToolbarWidgetView: View {
.redacted(reason: entry.isPlaceholder ? .placeholder : [])
HStack {
Image(systemName: entry.footerImage)
.resizable()
.font(Font.system(.body).weight(.light))
.scaledToFit()
.frame(width: 15, height: 15)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
Text(entry.footerText)
.font(.caption2)
.padding(.trailing, 13.0)
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brand))
.foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.brandElement))
}
.frame(maxWidth: geo.size.width - 5, maxHeight: geo.size.height - 2, alignment: .bottomTrailing)
}

Просмотреть файл

@ -47,7 +47,9 @@ struct DashboardWidget: Widget {
.supportedFamilies([.systemLarge])
.configurationDisplayName("Dashboard")
.description(NSLocalizedString("_description_dashboardwidget_", comment: ""))
#if !targetEnvironment(simulator)
.contentMarginsDisabled()
#endif
}
}
@ -61,7 +63,9 @@ struct FilesWidget: Widget {
.supportedFamilies([.systemLarge])
.configurationDisplayName("Files")
.description(NSLocalizedString("_description_fileswidget_", comment: ""))
#if !targetEnvironment(simulator)
.contentMarginsDisabled()
#endif
}
}
@ -75,7 +79,9 @@ struct ToolbarWidget: Widget {
.supportedFamilies([.systemMedium])
.configurationDisplayName("Toolbar")
.description(NSLocalizedString("_description_toolbarwidget_", comment: ""))
#if !targetEnvironment(simulator)
.contentMarginsDisabled()
#endif
}
}
@ -90,7 +96,9 @@ struct LockscreenWidget: Widget {
.supportedFamilies([.accessoryRectangular, .accessoryCircular])
.configurationDisplayName(NSLocalizedString("_title_lockscreenwidget_", comment: ""))
.description(NSLocalizedString("_description_lockscreenwidget_", comment: ""))
#if !targetEnvironment(simulator)
.contentMarginsDisabled()
#endif
} else {
return EmptyWidgetConfiguration()
}
@ -99,6 +107,7 @@ struct LockscreenWidget: Widget {
extension View {
func widgetBackground(_ backgroundView: some View) -> some View {
#if !targetEnvironment(simulator)
if #available(iOSApplicationExtension 17.0, *) {
return containerBackground(for: .widget) {
backgroundView
@ -106,5 +115,9 @@ extension View {
} else {
return background(backgroundView)
}
#else
return background(backgroundView)
#endif
}
}

Просмотреть файл

@ -1,49 +1,17 @@
#!/usr/bin/env bash
#This script creates a testable Docker enviroment of the Nextcloud server, and is used by the CI for tests.
container_name="nextcloud_test"
port=8080
server_url="http://localhost:${port}"
user="admin"
CONTAINER_NAME=nextcloud_test
SERVER_PORT=8080
TEST_BRANCH=stable28
SERVER_URL="http://localhost:${SERVER_PORT}"
USER="admin"
docker run --rm -d --name $container_name -p $port:80 ghcr.io/juliushaertl/nextcloud-dev-php80:latest
docker run --rm -d \
--name $CONTAINER_NAME \
-e SERVER_BRANCH=$TEST_BRANCH \
-p $SERVER_PORT:80 \
ghcr.io/juliushaertl/nextcloud-dev-php80:latest
timeout=300
elapsed=0
echo "Waiting for server..."
sleep 2
while true; do
content=$(curl -s $server_url/status.php)
if [[ $content == *"installed\":true"* ]]; then
break
fi
elapsed=$((elapsed + 1))
if [ $elapsed -ge $timeout ]; then
echo "No success after $timeout seconds."
exit 1
fi
sleep 1
done
echo "Server is installed."
echo "Exporting env vars..."
sleep 2
password=$(docker exec -e NC_PASS=$user $container_name sudo -E -u www-data php /var/www/html/occ user:add-app-password $user --password-from-env | tail -1)
export TEST_APP_PASSWORD=$password
export TEST_SERVER_URL=$server_url
export TEST_USER=$user
echo "TEST_SERVER_URL: ${TEST_SERVER_URL}"
echo "TEST_USER: ${TEST_USER}"
echo "TEST_APP_PASSWORD: ${TEST_APP_PASSWORD}"
echo "Env vars exported."
source ./wait-for-server.sh

Просмотреть файл

@ -29,14 +29,7 @@ public protocol NCAccountRequestDelegate: AnyObject {
func accountRequestChangeAccount(account: String)
}
// optional func
public extension NCAccountRequestDelegate {
func accountRequestAddAccount() {}
func accountRequestChangeAccount(account: String) {}
}
class NCAccountRequest: UIViewController {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var progressView: UIProgressView!
@ -73,8 +66,8 @@ class NCAccountRequest: UIViewController {
progressView.isHidden = true
}
NotificationCenter.default.addObserver(self, selector: #selector(startTimer), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidEnterBackground), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(startTimer), name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
@ -97,7 +90,6 @@ class NCAccountRequest: UIViewController {
// MARK: - NotificationCenter
@objc func applicationDidEnterBackground() {
if dismissDidEnterBackground {
dismiss(animated: false)
}
@ -106,19 +98,17 @@ class NCAccountRequest: UIViewController {
// MARK: - Progress
@objc func startTimer() {
if enableTimerProgress {
time = 0
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateProgress), userInfo: nil, repeats: true)
progressView.isHidden = false
progressView?.isHidden = false
} else {
progressView.isHidden = true
progressView?.isHidden = true
}
}
@objc func updateProgress() {
time += 0.1
if time >= secondsAutoDismiss {
dismiss(animated: true)
@ -129,9 +119,7 @@ class NCAccountRequest: UIViewController {
}
extension NCAccountRequest: UITableViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
timer?.invalidate()
progressView.progress = 0
}
@ -141,14 +129,10 @@ extension NCAccountRequest: UITableViewDelegate {
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == accounts.count {
dismiss(animated: true)
delegate?.accountRequestAddAccount()
} else {
let account = accounts[indexPath.row]
if account.account != activeAccount?.account {
dismiss(animated: true) {
@ -162,7 +146,6 @@ extension NCAccountRequest: UITableViewDelegate {
}
extension NCAccountRequest: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if enableAddAccount {
return accounts.count + 1
@ -172,10 +155,8 @@ extension NCAccountRequest: UITableViewDataSource {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.backgroundColor = tableView.backgroundColor
let avatarImage = cell.viewWithTag(10) as? UIImageView
let userLabel = cell.viewWithTag(20) as? UILabel
let urlLabel = cell.viewWithTag(30) as? UILabel
@ -186,7 +167,7 @@ extension NCAccountRequest: UITableViewDataSource {
if indexPath.row == accounts.count {
avatarImage?.image = utility.loadImage(named: "plus").image(color: .systemBlue, size: 15)
avatarImage?.image = utility.loadImage(named: "plus", colors: [.systemBlue])
avatarImage?.contentMode = .center
userLabel?.text = NSLocalizedString("_add_account_", comment: "")
userLabel?.textColor = .systemBlue
@ -209,7 +190,7 @@ extension NCAccountRequest: UITableViewDataSource {
}
if account.active {
activeImage?.image = utility.loadImage(named: "checkmark").image(color: .systemBlue, size: 30)
activeImage?.image = utility.loadImage(named: "checkmark", colors: [.systemBlue])
} else {
activeImage?.image = nil
}

Просмотреть файл

@ -44,6 +44,7 @@ class NCActivity: UIViewController, NCSharePagingContent {
let utility = NCUtility()
var allItems: [DateCompareable] = []
var sectionDates: [Date] = []
var dataSourceTask: URLSessionTask?
var insets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
var didSelectItemEnable: Bool = true
@ -102,9 +103,12 @@ class NCActivity: UIViewController, NCSharePagingContent {
fetchAll(isInitial: true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
appDelegate.activeViewController = self
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Cancel Queue & Retrieves Properties
NCNetworking.shared.downloadThumbnailActivityQueue.cancelAll()
dataSourceTask?.cancel()
}
override func viewWillLayoutSubviews() {
@ -123,7 +127,7 @@ class NCActivity: UIViewController, NCSharePagingContent {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 15)
label.textColor = UIColor.systemGray
label.textColor = NCBrandColor.shared.textColor2
label.textAlignment = .center
label.text = NSLocalizedString("_no_activity_footer_", comment: "")
label.frame = CGRect(x: 0, y: 10, width: tableView.frame.width, height: 60)
@ -156,7 +160,7 @@ extension NCActivity: UITableViewDelegate {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 13)
label.textColor = .label
label.textColor = NCBrandColor.shared.textColor
label.text = utility.getTitleFromDate(sectionDates[section])
label.textAlignment = .center
label.layer.cornerRadius = 11
@ -209,16 +213,16 @@ extension NCActivity: UITableViewDataSource {
// Image
let fileName = appDelegate.userBaseUrl + "-" + comment.actorId + ".png"
NCNetworking.shared.downloadAvatar(user: comment.actorId, dispalyName: comment.actorDisplayName, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
NCNetworking.shared.downloadAvatar(user: comment.actorId, dispalyName: comment.actorDisplayName, fileName: fileName, cell: cell, view: tableView)
// Username
cell.labelUser.text = comment.actorDisplayName
cell.labelUser.textColor = .label
cell.labelUser.textColor = NCBrandColor.shared.textColor
// Date
cell.labelDate.text = utility.dateDiff(comment.creationDateTime as Date)
cell.labelDate.textColor = .systemGray4
// Message
cell.labelMessage.text = comment.message
cell.labelMessage.textColor = .label
cell.labelMessage.textColor = NCBrandColor.shared.textColor
// Button Menu
if comment.actorId == appDelegate.userId {
cell.buttonMenu.isHidden = false
@ -242,7 +246,7 @@ extension NCActivity: UITableViewDataSource {
cell.avatar.isHidden = true
cell.subjectTrailingConstraint.constant = 10
cell.didSelectItemEnable = self.didSelectItemEnable
cell.subject.textColor = .label
cell.subject.textColor = NCBrandColor.shared.textColor
cell.viewController = self
// icon
@ -276,7 +280,7 @@ extension NCActivity: UITableViewDataSource {
let fileName = appDelegate.userBaseUrl + "-" + activity.user + ".png"
NCNetworking.shared.downloadAvatar(user: activity.user, dispalyName: nil, fileName: fileName, cell: cell, view: tableView, cellImageView: cell.fileAvatarImageView)
NCNetworking.shared.downloadAvatar(user: activity.user, dispalyName: nil, fileName: fileName, cell: cell, view: tableView)
}
// subject
@ -426,7 +430,9 @@ extension NCActivity {
limit: 1,
objectId: nil,
objectType: objectType,
previews: true) { account, _, activityFirstKnown, activityLastGiven, _, error in
previews: true) { task in
self.dataSourceTask = task
} completion: { account, _, activityFirstKnown, activityLastGiven, _, error in
defer { disptachGroup.leave() }
let largestActivityId = max(activityFirstKnown, activityLastGiven)
@ -453,7 +459,9 @@ extension NCActivity {
limit: min(limit, 200),
objectId: metadata?.fileId,
objectType: objectType,
previews: true) { account, activities, activityFirstKnown, activityLastGiven, _, error in
previews: true) { task in
self.dataSourceTask = task
} completion: { account, activities, activityFirstKnown, activityLastGiven, _, error in
defer { disptachGroup.leave() }
guard error == .success,
account == self.appDelegate.account,
@ -494,7 +502,7 @@ extension NCActivity: NCShareCommentsCellDelegate {
actions.append(
NCMenuAction(
title: NSLocalizedString("_edit_comment_", comment: ""),
icon: UIImage(named: "pencil")!.image(color: UIColor.systemGray, size: 50),
icon: utility.loadImage(named: "pencil", colors: [NCBrandColor.shared.iconImageColor]),
action: { _ in
guard let metadata = self.metadata, let tableComments = tableComments else { return }
@ -525,7 +533,8 @@ extension NCActivity: NCShareCommentsCellDelegate {
actions.append(
NCMenuAction(
title: NSLocalizedString("_delete_comment_", comment: ""),
icon: utility.loadImage(named: "trash"),
destructive: true,
icon: utility.loadImage(named: "trash", colors: [.red]),
action: { _ in
guard let metadata = self.metadata, let tableComments = tableComments else { return }

Просмотреть файл

@ -40,7 +40,7 @@ class NCActivityCommentView: UIView, UITextFieldDelegate {
if let image = UIImage(contentsOfFile: fileNameLocalPath) {
imageItem.image = image
} else {
imageItem.image = UIImage(named: "avatar")
imageItem.image = NCUtility().loadImage(named: "person.crop.circle", colors: [NCBrandColor.shared.iconImageColor])
}
if account.displayName.isEmpty {
@ -48,7 +48,7 @@ class NCActivityCommentView: UIView, UITextFieldDelegate {
} else {
labelUser.text = account.displayName
}
labelUser.textColor = .label
labelUser.textColor = NCBrandColor.shared.textColor
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {

Просмотреть файл

@ -166,7 +166,7 @@ extension NCActivityTableViewCell: UICollectionViewDataSource {
if let imageNamePath = imageNamePath, id == self.idActivity, let image = UIImage(contentsOfFile: imageNamePath) {
cell.imageView.image = image
} else {
cell.imageView.image = UIImage(named: "file")
cell.imageView.image = NCImageCache.images.file
}
}
@ -176,11 +176,11 @@ extension NCActivityTableViewCell: UICollectionViewDataSource {
let source = activityPreview.source
utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 100, rewrite: false, account: appDelegate.account, id: idActivity) { imageNamePath, id in
utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 150, rewrite: false, account: appDelegate.account, id: idActivity) { imageNamePath, id in
if let imageNamePath = imageNamePath, id == self.idActivity, let image = UIImage(contentsOfFile: imageNamePath) {
cell.imageView.image = image
} else {
cell.imageView.image = UIImage(named: "file")
cell.imageView.image = NCImageCache.images.file
}
}
@ -192,8 +192,10 @@ extension NCActivityTableViewCell: UICollectionViewDataSource {
if FileManager.default.fileExists(atPath: fileNamePath), let image = UIImage(contentsOfFile: fileNamePath) {
cell.imageView.image = image
cell.imageView?.contentMode = .scaleAspectFill
} else {
cell.imageView?.image = UIImage(named: "file_photo")
cell.imageView?.image = utility.loadImage(named: "doc", colors: [NCBrandColor.shared.iconImageColor])
cell.imageView?.contentMode = .scaleAspectFit
cell.fileId = fileId
if !FileManager.default.fileExists(atPath: fileNamePath) {
if NCNetworking.shared.downloadThumbnailActivityQueue.operations.filter({ ($0 as? NCOperationDownloadThumbnailActivity)?.fileId == fileId }).isEmpty {

Просмотреть файл

@ -24,41 +24,32 @@
import UIKit
import BackgroundTasks
import NextcloudKit
import TOPasscodeViewController
import LocalAuthentication
import Firebase
import WidgetKit
import Queuer
import EasyTipView
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, NCUserBaseUrl {
var backgroundSessionCompletionHandler: (() -> Void)?
var window: UIWindow?
@objc var account: String = ""
@objc var urlBase: String = ""
@objc var user: String = ""
@objc var userId: String = ""
@objc var password: String = ""
var tipView: EasyTipView?
var backgroundSessionCompletionHandler: (() -> Void)?
var activeLogin: NCLogin?
var activeLoginWeb: NCLoginWeb?
var activeServerUrl: String = ""
@objc var activeViewController: UIViewController?
var mainTabBar: NCMainTabBar?
var activeMetadata: tableMetadata?
let listFilesVC = ThreadSafeDictionary<String, NCFiles>()
var disableSharesView: Bool = false
var documentPickerViewController: NCDocumentPickerViewController?
var timerErrorNetworking: Timer?
var isAppRefresh: Bool = false
var isProcessingTask: Bool = false
var timerErrorNetworkingDisabled: Bool = false
var taskAutoUploadDate: Date = Date()
var isUiTestingEnabled: Bool {
return ProcessInfo.processInfo.arguments.contains("UI_TESTING")
}
var notificationSettings: UNNotificationSettings?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if isUiTestingEnabled {
@ -87,8 +78,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
NextcloudKit.shared.setup(delegate: NCNetworking.shared)
NextcloudKit.shared.setup(userAgent: userAgent)
startTimerErrorNetworking()
var levelLog = 0
NextcloudKit.shared.nkCommonInstance.pathLog = utilityFileSystem.directoryGroup
@ -105,14 +94,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start session with level \(levelLog) " + versionNextcloudiOS)
}
if let account = NCManageDatabase.shared.getActiveAccount() {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Account active \(account.account)")
if NCKeychain().getPassword(account: account.account).isEmpty {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] PASSWORD NOT FOUND for \(account.account)")
}
}
if let activeAccount = NCManageDatabase.shared.getActiveAccount() {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Account active \(activeAccount.account)")
if NCKeychain().getPassword(account: activeAccount.account).isEmpty {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] PASSWORD NOT FOUND for \(activeAccount.account)")
}
account = activeAccount.account
urlBase = activeAccount.urlBase
@ -140,6 +126,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
NCImageCache.shared.createImagesCache()
// Push Notification & display notification
UNUserNotificationCenter.current().getNotificationSettings { settings in
self.notificationSettings = settings
}
application.registerForRemoteNotifications()
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in }
@ -158,127 +147,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
self.handleProcessingTask(task)
}
if account.isEmpty {
if NCBrandOptions.shared.disable_intro {
openLogin(viewController: nil, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
} else {
if let viewController = UIStoryboard(name: "NCIntro", bundle: nil).instantiateInitialViewController() {
let navigationController = NCLoginNavigationController(rootViewController: viewController)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
}
}
} else {
NCPasscode.shared.presentPasscode(delegate: self) {
NCPasscode.shared.enableTouchFaceID()
}
}
return true
}
// MARK: - Life Cycle
// L' applicazione entrerà in attivo (sempre)
func applicationDidBecomeActive(_ application: UIApplication) {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application did become active")
NCSettingsBundleHelper.setVersionAndBuildNumber()
NCSettingsBundleHelper.checkAndExecuteSettings(delay: 0.5)
// START TIMER UPLOAD PROCESS
NCNetworkingProcess.shared.startTimer()
if !NCAskAuthorization().isRequesting {
NCPasscode.shared.hidePrivacyProtectionWindow()
}
NCService().startRequestServicesServer()
NCAutoUpload.shared.initAutoUpload(viewController: nil) { items in
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Initialize Auto upload with \(items) uploads")
}
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationDidBecomeActive)
}
// L' applicazione si dimetterà dallo stato di attivo
func applicationWillResignActive(_ application: UIApplication) {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application will resign active")
guard !account.isEmpty else { return }
// STOP TIMER UPLOAD PROCESS
NCNetworkingProcess.shared.stopTimer()
if NCKeychain().privacyScreenEnabled {
NCPasscode.shared.showPrivacyProtectionWindow()
}
// Reload Widget
WidgetCenter.shared.reloadAllTimelines()
// Clear older files
let days = NCKeychain().cleanUpDay
let utilityFileSystem = NCUtilityFileSystem()
utilityFileSystem.cleanUp(directory: utilityFileSystem.directoryProviderStorage, days: TimeInterval(days))
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationWillResignActive)
}
// L' applicazione entrerà in primo piano (dopo il background)
func applicationWillEnterForeground(_ application: UIApplication) {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application will enter in foreground")
guard !account.isEmpty else { return }
NCPasscode.shared.enableTouchFaceID()
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationWillEnterForeground)
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRichdocumentGrabFocus)
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSourceNetwork, second: 2)
}
// L' applicazione è entrata nello sfondo
func applicationDidEnterBackground(_ application: UIApplication) {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application did enter in background")
guard !account.isEmpty else { return }
let activeAccount = NCManageDatabase.shared.getActiveAccount()
if let autoUpload = activeAccount?.autoUpload, autoUpload {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Auto upload: true")
if UIApplication.shared.backgroundRefreshStatus == .available {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Auto upload in background: true")
} else {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Auto upload in background: false")
}
} else {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Auto upload: false")
}
if let error = updateShareAccounts() {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Create share accounts \(error.localizedDescription)")
}
scheduleAppRefresh()
scheduleAppProcessing()
NCNetworking.shared.cancelAllQueue()
NCNetworking.shared.cancelDownloadTasks()
NCNetworking.shared.cancelUploadTasks()
NCPasscode.shared.presentPasscode(delegate: self) { }
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationDidEnterBackground)
}
// L'applicazione terminerà
func applicationWillTerminate(_ application: UIApplication) {
if UIApplication.shared.backgroundRefreshStatus == .available {
if self.notificationSettings?.authorizationStatus != .denied && UIApplication.shared.backgroundRefreshStatus == .available {
let content = UNMutableNotificationContent()
content.title = NCBrandOptions.shared.brand
content.body = NSLocalizedString("_keep_running_", comment: "")
@ -290,11 +163,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] bye bye")
}
// MARK: - UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
// MARK: - Background Task
/*
@discussion Schedule a refresh task request to ask that the system launch your app briefly so that you can download data and keep your app's contents up-to-date. The system will fulfill this request intelligently based on system conditions and app usage.
< MAX 30 seconds >
*/
func scheduleAppRefresh() {
@ -302,7 +188,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
request.earliestBeginDate = Date(timeIntervalSinceNow: 60) // Refresh after 60 seconds.
do {
try BGTaskScheduler.shared.submit(request)
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Refresh task: ok")
} catch {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Refresh task failed to submit request: \(error)")
}
@ -310,7 +195,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
/*
@discussion Schedule a processing task request to ask that the system launch your app when conditions are favorable for battery life to handle deferrable, longer-running processing, such as syncing, database maintenance, or similar tasks. The system will attempt to fulfill this request to the best of its ability within the next two days as long as the user has used your app within the past week.
< MAX over 1 minute >
*/
func scheduleAppProcessing() {
@ -320,7 +204,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
request.requiresExternalPower = false
do {
try BGTaskScheduler.shared.submit(request)
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Processing task: ok")
} catch {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Background Processing task failed to submit request: \(error)")
}
@ -329,59 +212,57 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func handleAppRefresh(_ task: BGTask) {
scheduleAppRefresh()
if isProcessingTask {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] ProcessingTask already in progress, abort.")
return task.setTaskCompleted(success: true)
}
isAppRefresh = true
handleAppRefreshProcessingTask(taskText: "AppRefresh") {
task.setTaskCompleted(success: true)
self.isAppRefresh = false
}
}
func handleProcessingTask(_ task: BGTask) {
scheduleAppProcessing()
if isAppRefresh {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] AppRefresh already in progress, abort.")
return task.setTaskCompleted(success: true)
}
isProcessingTask = true
handleAppRefreshProcessingTask(taskText: "ProcessingTask") {
task.setTaskCompleted(success: true)
self.isProcessingTask = false
}
}
func handleAppRefreshProcessingTask(taskText: String, completion: @escaping () -> Void = {}) {
Task {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) start handle")
let items = await NCAutoUpload.shared.initAutoUpload()
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) auto upload with \(items) uploads")
let results = await NCNetworkingProcess.shared.start()
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) networking process with download: \(results.counterDownloading) upload: \(results.counterUploading)")
var itemsAutoUpload = 0
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) start handle")
// Test every > 1 min
if Date() > self.taskAutoUploadDate.addingTimeInterval(60) {
self.taskAutoUploadDate = Date()
itemsAutoUpload = await NCAutoUpload.shared.initAutoUpload()
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) auto upload with \(itemsAutoUpload) uploads")
} else {
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) disabled auto upload")
}
let results = await NCNetworkingProcess.shared.start(scene: nil)
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) networking process with download: \(results.counterDownloading) upload: \(results.counterUploading)")
if taskText == "ProcessingTask",
items == 0, results.counterDownloading == 0, results.counterUploading == 0,
itemsAutoUpload == 0,
results.counterDownloading == 0,
results.counterUploading == 0,
let directories = NCManageDatabase.shared.getTablesDirectory(predicate: NSPredicate(format: "account == %@ AND offline == true", self.account), sorted: "offlineDate", ascending: true) {
for directory: tableDirectory in directories {
// only 3 time for day
// test only 3 time for day (every 8 h.)
if let offlineDate = directory.offlineDate, offlineDate.addingTimeInterval(28800) > Date() {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) skip synchronization for \(directory.serverUrl) in date \(offlineDate)")
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) skip synchronization for \(directory.serverUrl) in date \(offlineDate)")
continue
}
let results = await NCNetworking.shared.synchronization(account: self.account, serverUrl: directory.serverUrl, add: false)
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) end synchronization for \(directory.serverUrl), errorCode: \(results.errorCode), item: \(results.items)")
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) end synchronization for \(directory.serverUrl), errorCode: \(results.errorCode), item: \(results.items)")
}
}
let counter = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "account == %@ AND (session == %@ || session == %@) AND status != %d", self.account, NCNetworking.shared.sessionDownloadBackground, NCNetworking.shared.sessionUploadBackground, NCGlobal.shared.metadataStatusNormal))?.count ?? 0
UIApplication.shared.applicationIconBadgeNumber = counter
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] \(taskText) completion handle")
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] \(taskText) completion handle")
completion()
}
}
@ -390,7 +271,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Start handle Events For Background URLSession: \(identifier)")
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] Start handle Events For Background URLSession: \(identifier)")
WidgetCenter.shared.reloadAllTimelines()
backgroundSessionCompletionHandler = completionHandler
}
@ -413,7 +294,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
NCNetworking.shared.checkPushNotificationServerProxyCertificateUntrusted(viewController: self.window?.rootViewController) { error in
NCNetworking.shared.checkPushNotificationServerProxyCertificateUntrusted(viewController: UIApplication.shared.firstWindow?.rootViewController) { error in
if error == .success {
NCPushNotification.shared().registerForRemoteNotifications(withDeviceToken: deviceToken)
}
@ -446,35 +327,51 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let navigationController = UINavigationController(rootViewController: viewController)
navigationController.modalPresentationStyle = .fullScreen
self.window?.rootViewController?.present(navigationController, animated: true)
UIApplication.shared.firstWindow?.rootViewController?.present(navigationController, animated: true)
}
} else if !findAccount {
let message = NSLocalizedString("_the_account_", comment: "") + " " + accountPush + " " + NSLocalizedString("_does_not_exist_", comment: "")
let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
self.window?.rootViewController?.present(alertController, animated: true, completion: { })
UIApplication.shared.firstWindow?.rootViewController?.present(alertController, animated: true, completion: { })
}
}
}
// MARK: - Login & checkErrorNetworking
// MARK: - Login
@objc func openLogin(viewController: UIViewController?, selector: Int, openLoginWeb: Bool) {
@objc func openLogin(selector: Int, openLoginWeb: Bool, windowForRootViewController: UIWindow? = nil) {
func showLoginViewController(_ viewController: UIViewController?) {
guard let viewController else { return }
let navigationController = NCLoginNavigationController(rootViewController: viewController)
navigationController.modalPresentationStyle = .fullScreen
navigationController.navigationBar.barStyle = .black
navigationController.navigationBar.tintColor = NCBrandColor.shared.customerText
navigationController.navigationBar.barTintColor = NCBrandColor.shared.customer
navigationController.navigationBar.isTranslucent = false
if let window = windowForRootViewController {
window.rootViewController = navigationController
window.makeKeyAndVisible()
} else {
UIApplication.shared.allSceneSessionDestructionExceptFirst()
UIApplication.shared.firstWindow?.rootViewController?.present(navigationController, animated: true)
}
}
// [WEBPersonalized] [AppConfig]
if NCBrandOptions.shared.use_login_web_personalized || NCBrandOptions.shared.use_AppConfig {
if activeLoginWeb?.view.window == nil {
activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb
activeLoginWeb?.urlBase = NCBrandOptions.shared.loginBaseUrl
showLoginViewController(activeLoginWeb, contextViewController: viewController)
showLoginViewController(activeLoginWeb)
}
return
}
// Nextcloud standard login
if selector == NCGlobal.shared.introSignup {
if activeLoginWeb?.view.window == nil {
activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb
if selector == NCGlobal.shared.introSignup {
@ -482,72 +379,45 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
} else {
activeLoginWeb?.urlBase = self.urlBase
}
showLoginViewController(activeLoginWeb, contextViewController: viewController)
showLoginViewController(activeLoginWeb)
}
} else if NCBrandOptions.shared.disable_intro && NCBrandOptions.shared.disable_request_login_url {
if activeLoginWeb?.view.window == nil {
activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb
activeLoginWeb?.urlBase = NCBrandOptions.shared.loginBaseUrl
showLoginViewController(activeLoginWeb, contextViewController: viewController)
showLoginViewController(activeLoginWeb)
}
} else if openLoginWeb {
// Used also for reinsert the account (change passwd)
if activeLoginWeb?.view.window == nil {
activeLoginWeb = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLoginWeb") as? NCLoginWeb
activeLoginWeb?.urlBase = urlBase
activeLoginWeb?.user = user
showLoginViewController(activeLoginWeb, contextViewController: viewController)
showLoginViewController(activeLoginWeb)
}
} else {
if activeLogin?.view.window == nil {
activeLogin = UIStoryboard(name: "NCLogin", bundle: nil).instantiateViewController(withIdentifier: "NCLogin") as? NCLogin
showLoginViewController(activeLogin, contextViewController: viewController)
showLoginViewController(activeLogin)
}
}
}
func showLoginViewController(_ viewController: UIViewController?, contextViewController: UIViewController?) {
// MARK: - Error Networking
if contextViewController == nil {
if let viewController = viewController {
let navigationController = NCLoginNavigationController(rootViewController: viewController)
navigationController.navigationBar.barStyle = .black
navigationController.navigationBar.tintColor = NCBrandColor.shared.customerText
navigationController.navigationBar.barTintColor = NCBrandColor.shared.customer
navigationController.navigationBar.isTranslucent = false
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
}
} else if contextViewController is UINavigationController {
if let contextViewController = contextViewController, let viewController = viewController {
(contextViewController as? UINavigationController)?.pushViewController(viewController, animated: true)
}
} else {
if let viewController = viewController, let contextViewController = contextViewController {
let navigationController = NCLoginNavigationController(rootViewController: viewController)
navigationController.modalPresentationStyle = .fullScreen
navigationController.navigationBar.barStyle = .black
navigationController.navigationBar.tintColor = NCBrandColor.shared.customerText
navigationController.navigationBar.barTintColor = NCBrandColor.shared.customer
navigationController.navigationBar.isTranslucent = false
contextViewController.present(navigationController, animated: true) { }
}
}
@objc func startTimerErrorNetworking(scene: UIScene) {
timerErrorNetworkingDisabled = false
timerErrorNetworking = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(checkErrorNetworking(_:)), userInfo: nil, repeats: true)
}
@objc func startTimerErrorNetworking() {
timerErrorNetworking = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(checkErrorNetworking), userInfo: nil, repeats: true)
}
@objc private func checkErrorNetworking() {
guard !account.isEmpty, NCKeychain().getPassword(account: account).isEmpty else { return }
openLogin(viewController: window?.rootViewController, selector: NCGlobal.shared.introLogin, openLoginWeb: true)
@objc private func checkErrorNetworking(_ notification: NSNotification) {
guard !self.timerErrorNetworkingDisabled,
!account.isEmpty,
NCKeychain().getPassword(account: account).isEmpty else { return }
openLogin(selector: NCGlobal.shared.introLogin, openLoginWeb: true)
}
func trustCertificateError(host: String) {
@ -578,11 +448,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
let viewController = navigationController.topViewController as? NCViewCertificateDetails {
viewController.delegate = self
viewController.host = host
self.window?.rootViewController?.present(navigationController, animated: true)
UIApplication.shared.firstWindow?.rootViewController?.present(navigationController, animated: true)
}
}))
window?.rootViewController?.present(alertController, animated: true)
UIApplication.shared.firstWindow?.rootViewController?.present(alertController, animated: true)
}
// MARK: - Account
@ -632,6 +502,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
@objc func deleteAccount(_ account: String, wipe: Bool) {
UIApplication.shared.allSceneSessionDestructionExceptFirst()
if let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) {
NCPushNotification.shared().unsubscribingNextcloudServerPushNotification(account.account, urlBase: account.urlBase, user: account.user, withSubscribing: false)
}
@ -664,7 +536,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
self.changeAccount(newAccount, userProfile: nil)
}
} else {
openLogin(viewController: window?.rootViewController, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
openLogin(selector: NCGlobal.shared.introLogin, openLoginWeb: false)
}
}
}
@ -719,175 +591,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
let applicationHandle = NCApplicationHandle()
return applicationHandle.applicationOpenUserActivity(userActivity)
}
// MARK: - Scheme URL
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
let scheme = url.scheme
let action = url.host
var fileName: String = ""
var serverUrl: String = ""
/*
Example: nextcloud://open-action?action=create-voice-memo&&user=marinofaggiana&url=https://cloud.nextcloud.com
*/
if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-action" {
if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) {
let queryItems = urlComponents.queryItems
guard let actionScheme = queryItems?.filter({ $0.name == "action" }).first?.value,
let userScheme = queryItems?.filter({ $0.name == "user" }).first?.value,
let urlScheme = queryItems?.filter({ $0.name == "url" }).first?.value,
let rootViewController = window?.rootViewController else { return false }
if getMatchedAccount(userId: userScheme, url: urlScheme) == nil {
let message = NSLocalizedString("_the_account_", comment: "") + " " + userScheme + NSLocalizedString("_of_", comment: "") + " " + urlScheme + " " + NSLocalizedString("_does_not_exist_", comment: "")
let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
window?.rootViewController?.present(alertController, animated: true, completion: { })
return false
}
switch actionScheme {
case NCGlobal.shared.actionUploadAsset:
NCAskAuthorization().askAuthorizationPhotoLibrary(viewController: rootViewController) { hasPermission in
if hasPermission {
NCPhotosPickerViewController(viewController: rootViewController, maxSelectedAssets: 0, singleSelectedMode: false)
}
}
case NCGlobal.shared.actionScanDocument:
NCDocumentCamera.shared.openScannerDocument(viewController: rootViewController)
case NCGlobal.shared.actionTextDocument:
guard let navigationController = UIStoryboard(name: "NCCreateFormUploadDocuments", bundle: nil).instantiateInitialViewController(),
let directEditingCreators = NCManageDatabase.shared.getDirectEditingCreators(account: account),
let directEditingCreator = directEditingCreators.first(where: { $0.editor == NCGlobal.shared.editorText}),
let viewController = (navigationController as? UINavigationController)?.topViewController as? NCCreateFormUploadDocuments else { return false }
navigationController.modalPresentationStyle = UIModalPresentationStyle.formSheet
viewController.editorId = NCGlobal.shared.editorText
viewController.creatorId = directEditingCreator.identifier
viewController.typeTemplate = NCGlobal.shared.templateDocument
viewController.serverUrl = activeServerUrl
viewController.titleForm = NSLocalizedString("_create_nextcloudtext_document_", comment: "")
rootViewController.present(navigationController, animated: true, completion: nil)
case NCGlobal.shared.actionVoiceMemo:
NCAskAuthorization().askAuthorizationAudioRecord(viewController: rootViewController) { hasPermission in
if hasPermission {
if let viewController = UIStoryboard(name: "NCAudioRecorderViewController", bundle: nil).instantiateInitialViewController() as? NCAudioRecorderViewController {
viewController.modalTransitionStyle = .crossDissolve
viewController.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
rootViewController.present(viewController, animated: true, completion: nil)
}
}
}
default:
print("No action")
}
}
return true
}
/*
Example: nextcloud://open-file?path=Talk/IMG_0000123.jpg&user=marinofaggiana&link=https://cloud.nextcloud.com/f/123
*/
else if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-file" {
if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) {
let queryItems = urlComponents.queryItems
guard let userScheme = queryItems?.filter({ $0.name == "user" }).first?.value,
let pathScheme = queryItems?.filter({ $0.name == "path" }).first?.value,
let linkScheme = queryItems?.filter({ $0.name == "link" }).first?.value else { return false}
guard let matchedAccount = getMatchedAccount(userId: userScheme, url: linkScheme) else {
guard let domain = URL(string: linkScheme)?.host else { return true }
fileName = (pathScheme as NSString).lastPathComponent
let message = String(format: NSLocalizedString("_account_not_available_", comment: ""), userScheme, domain, fileName)
let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
window?.rootViewController?.present(alertController, animated: true, completion: { })
return false
}
let davFiles = NextcloudKit.shared.nkCommonInstance.dav + "/files/" + self.userId
if pathScheme.contains("/") {
fileName = (pathScheme as NSString).lastPathComponent
serverUrl = matchedAccount.urlBase + "/" + davFiles + "/" + (pathScheme as NSString).deletingLastPathComponent
} else {
fileName = pathScheme
serverUrl = matchedAccount.urlBase + "/" + davFiles
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
NCActionCenter.shared.openFileViewInFolder(serverUrl: serverUrl, fileNameBlink: nil, fileNameOpen: fileName)
}
}
return true
/*
Example: nextcloud://open-and-switch-account?user=marinofaggiana&url=https://cloud.nextcloud.com
*/
} else if !account.isEmpty && scheme == NCGlobal.shared.appScheme && action == "open-and-switch-account" {
guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
let queryItems = urlComponents.queryItems
guard let userScheme = queryItems?.filter({ $0.name == "user" }).first?.value,
let urlScheme = queryItems?.filter({ $0.name == "url" }).first?.value else { return false}
// If the account doesn't exist, return false which will open the app without switching
if getMatchedAccount(userId: userScheme, url: urlScheme) == nil {
return false
}
// Otherwise open the app and switch accounts
return true
} else {
let applicationHandle = NCApplicationHandle()
let isHandled = applicationHandle.applicationOpenURL(url)
if isHandled {
return true
} else {
app.open(url)
return true
}
}
}
func getMatchedAccount(userId: String, url: String) -> tableAccount? {
if let activeAccount = NCManageDatabase.shared.getActiveAccount() {
let urlBase = URL(string: activeAccount.urlBase)
if url.contains(urlBase?.host ?? "") && userId == activeAccount.userId {
return activeAccount
} else {
let accounts = NCManageDatabase.shared.getAllAccount()
for account in accounts {
let urlBase = URL(string: account.urlBase)
if url.contains(urlBase?.host ?? "") && userId == account.userId {
changeAccount(account.account, userProfile: nil)
return account
}
}
}
}
return nil
}
}
// MARK: -
// MARK: - Extension
extension AppDelegate: NCViewCertificateDetailsDelegate {
func viewCertificateDetailsDismiss(host: String) {
@ -901,45 +607,3 @@ extension AppDelegate: NCCreateFormUploadConflictDelegate {
NCNetworkingProcess.shared.createProcessUploads(metadatas: metadatas)
}
}
extension AppDelegate: NCPasscodeDelegate {
func requestedAccount() {
guard !NCPasscode.shared.isPasscodePresented, NCKeychain().accountRequest else {
return
}
let accounts = NCManageDatabase.shared.getAllAccount()
if accounts.count > 1 {
if let viewController = UIStoryboard(name: "NCAccountRequest", bundle: nil).instantiateInitialViewController() as? NCAccountRequest {
viewController.activeAccount = NCManageDatabase.shared.getActiveAccount()
viewController.accounts = accounts
viewController.enableTimerProgress = true
viewController.enableAddAccount = false
viewController.dismissDidEnterBackground = false
viewController.delegate = self
let screenHeighMax = UIScreen.main.bounds.height - (UIScreen.main.bounds.height / 5)
let numberCell = accounts.count
let height = min(CGFloat(numberCell * Int(viewController.heightCell) + 45), screenHeighMax)
let popup = NCPopupViewController(contentController: viewController, popupWidth: 300, popupHeight: height + 20)
popup.backgroundAlpha = 0.8
window?.rootViewController?.present(popup, animated: true)
viewController.startTimer()
}
}
}
func passcodeReset(_ passcodeViewController: TOPasscodeViewController) {
resetApplication()
}
}
extension AppDelegate: NCAccountRequestDelegate {
func accountRequestChangeAccount(account: String) {
changeAccount(account, userProfile: nil)
}
}

Просмотреть файл

@ -0,0 +1,76 @@
//
// NCAssistantCreateNewTask.swift
// Nextcloud
//
// Created by Milen on 09.04.24.
// Copyright © 2024 Marino Faggiana. All rights reserved.
//
import SwiftUI
struct NCAssistantCreateNewTask: View {
@EnvironmentObject var model: NCAssistantTask
@State var text = ""
@FocusState private var inFocus: Bool
@Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text(model.selectedType?.description ?? "")
.frame(maxWidth: .infinity, alignment: .topLeading)
ZStack(alignment: .topLeading) {
if text.isEmpty {
Text(NSLocalizedString("_input_", comment: ""))
.padding(24)
.foregroundStyle(.secondary)
}
TextEditor(text: $text)
.frame(maxWidth: .infinity, alignment: .topLeading)
.padding()
.transparentScrolling()
.background(Color(NCBrandColor.shared.textColor2).opacity(0.1))
.focused($inFocus)
}
.background(Color(NCBrandColor.shared.textColor2).opacity(0.1))
.clipShape(.rect(cornerRadius: 8))
}
.toolbar {
Button(action: {
model.scheduleTask(input: text)
presentationMode.wrappedValue.dismiss()
}, label: {
Text(NSLocalizedString("_create_", comment: ""))
})
.disabled(text.isEmpty)
}
.navigationTitle(String(format: NSLocalizedString("_new_task_", comment: ""), model.selectedType?.name ?? ""))
.navigationBarTitleDisplayMode(.inline)
.padding()
.onAppear {
inFocus = true
}
}
}
#Preview {
let model = NCAssistantTask()
return NCAssistantCreateNewTask()
.environmentObject(model)
.onAppear {
model.loadDummyData()
}}
private extension View {
func transparentScrolling() -> some View {
if #available(iOS 16.0, *) {
return scrollContentBackground(.hidden)
} else {
return onAppear {
UITextView.appearance().backgroundColor = .clear
}
}
}
}

Просмотреть файл

@ -0,0 +1,186 @@
//
// NCAssistantModel.swift
// Nextcloud
//
// Created by Milen on 08.04.24.
// Copyright © 2024 Marino Faggiana. All rights reserved.
//
import Foundation
import NextcloudKit
import SwiftUI
class NCAssistantTask: ObservableObject {
@Published var types: [NKTextProcessingTaskType] = []
@Published var filteredTasks: [NKTextProcessingTask] = []
@Published var selectedType: NKTextProcessingTaskType?
@Published var selectedTask: NKTextProcessingTask?
@Published var hasError: Bool = false
@Published var isLoading: Bool = false
private var tasks: [NKTextProcessingTask] = []
private let excludedTypeIds = ["OCA\\ContextChat\\TextProcessing\\ContextChatTaskType"]
init() {
load()
}
func load() {
loadAllTypes()
}
func filterTasks(ofType type: NKTextProcessingTaskType?) {
if let type {
self.filteredTasks = tasks.filter({ $0.type == type.id })
} else {
self.filteredTasks = tasks
}
self.filteredTasks = filteredTasks.sorted(by: { $0.completionExpectedAt ?? 0 > $1.completionExpectedAt ?? 0 })
}
func selectTaskType(_ type: NKTextProcessingTaskType?) {
selectedType = type
filterTasks(ofType: self.selectedType)
}
func selectTask(_ task: NKTextProcessingTask) {
selectedTask = task
guard let id = task.id else { return }
isLoading = true
NextcloudKit.shared.textProcessingGetTask(taskId: id) { _, task, _, error in
self.isLoading = false
if error != .success {
self.hasError = true
return
}
self.selectedTask = task
}
}
func scheduleTask(input: String) {
isLoading = true
NextcloudKit.shared.textProcessingSchedule(input: input, typeId: selectedType?.id ?? "", identifier: "assistant") { _, task, _, error in
self.isLoading = false
if error != .success {
self.hasError = true
return
}
guard let task else { return }
withAnimation {
self.tasks.insert(task, at: 0)
self.filteredTasks.insert(task, at: 0)
}
}
}
func deleteTask(_ task: NKTextProcessingTask) {
guard let id = task.id else { return }
isLoading = true
NextcloudKit.shared.textProcessingDeleteTask(taskId: id) { _, task, _, error in
self.isLoading = false
if error != .success {
self.hasError = true
return
}
withAnimation {
self.tasks.removeAll(where: { $0.id == task?.id })
self.filteredTasks.removeAll(where: { $0.id == task?.id })
}
}
}
private func loadAllTypes() {
isLoading = true
NextcloudKit.shared.textProcessingGetTypes { _, types, _, error in
self.isLoading = false
if error != .success {
self.hasError = true
return
}
guard let filteredTypes = types?.filter({ !self.excludedTypeIds.contains($0.id ?? "")}), !filteredTypes.isEmpty else { return }
withAnimation {
self.types = filteredTypes
}
if self.selectedType == nil {
self.selectTaskType(filteredTypes.first)
}
self.loadAllTasks()
}
}
private func loadAllTasks(appId: String = "assistant") {
isLoading = true
NextcloudKit.shared.textProcessingTaskList(appId: appId) { _, tasks, _, error in
self.isLoading = false
if error != .success {
self.hasError = true
return
}
guard let tasks = tasks else { return }
self.tasks = tasks
self.filterTasks(ofType: self.selectedType)
}
}
}
extension NCAssistantTask {
public func loadDummyData() {
let loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
var tasks: [NKTextProcessingTask] = []
for index in 1...10 {
tasks.append(NKTextProcessingTask(id: index, type: "OCP\\TextProcessing\\FreePromptTaskType", status: index, userId: "christine", appId: "assistant", input: loremIpsum, output: loremIpsum, identifier: "", completionExpectedAt: 1712666412))
}
self.types = [
NKTextProcessingTaskType(id: "1", name: "Free Prompt", description: ""),
NKTextProcessingTaskType(id: "2", name: "Summarize", description: ""),
NKTextProcessingTaskType(id: "3", name: "Generate headline", description: ""),
NKTextProcessingTaskType(id: "4", name: "Reformulate", description: "")
]
self.tasks = tasks
self.filteredTasks = tasks
self.selectedType = types[0]
self.selectedTask = filteredTasks[0]
}
}
extension NKTextProcessingTask {
struct StatusInfo {
let stringKey, imageSystemName: String
}
var statusInfo: StatusInfo {
return switch status {
case 1: StatusInfo(stringKey: "_assistant_task_scheduled_", imageSystemName: "clock")
case 2: StatusInfo(stringKey: "_assistant_task_in_progress_", imageSystemName: "clock.badge")
case 3: StatusInfo(stringKey: "_assistant_task_completed_", imageSystemName: "checkmark.circle")
case 4: StatusInfo(stringKey: "_assistant_task_failed_", imageSystemName: "exclamationmark.circle")
default: StatusInfo(stringKey: "_assistant_task_unknown_", imageSystemName: "questionmark.circle")
}
}
}

Просмотреть файл

@ -0,0 +1,195 @@
//
// NCAssistant.swift
// Nextcloud
//
// Created by Milen on 03.04.24.
// Copyright © 2024 Marino Faggiana. All rights reserved.
//
import SwiftUI
import NextcloudKit
import PopupView
struct NCAssistant: View {
@EnvironmentObject var model: NCAssistantTask
@State var presentNewTaskDialog = false
@State var input = ""
@Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
ZStack {
TaskList()
if model.types.isEmpty, !model.isLoading {
NCAssistantEmptyView(titleKey: "_no_types_", subtitleKey: "_no_types_subtitle_")
} else if model.filteredTasks.isEmpty, !model.isLoading {
NCAssistantEmptyView(titleKey: "_no_tasks_", subtitleKey: "_create_task_subtitle_")
}
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button(NSLocalizedString("_close_", comment: "")) {
presentationMode.wrappedValue.dismiss()
}
}
ToolbarItem(placement: .topBarTrailing) {
NavigationLink(destination: NCAssistantCreateNewTask()) {
Image(systemName: "plus")
.font(Font.system(.body).weight(.light))
}
.disabled(model.selectedType == nil)
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(NSLocalizedString("_assistant_", comment: ""))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.safeAreaInset(edge: .top, spacing: -10) {
ScrollViewReader { scrollProxy in
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(model.types, id: \.id) { type in
TypeButton(taskType: type, scrollProxy: scrollProxy)
}
}
.padding(20)
.frame(height: 50)
}
}
}
}
.navigationViewStyle(.stack)
.popup(isPresented: $model.hasError) {
Text(NSLocalizedString("_error_occurred_", comment: ""))
.padding()
.background(.red)
.cornerRadius(30.0)
} customize: {
$0
.type(.floater())
.autohideIn(2)
.position(.bottom)
}
.accentColor(Color(NCBrandColor.shared.iconImageColor))
.environmentObject(model)
}
}
#Preview {
let model = NCAssistantTask()
return NCAssistant()
.environmentObject(model)
.onAppear {
model.loadDummyData()
}
}
struct TaskList: View {
@EnvironmentObject var model: NCAssistantTask
var body: some View {
List(model.filteredTasks, id: \.id) { task in
TaskItem(task: task)
}
.if(!model.types.isEmpty) { view in
view.refreshable {
model.load()
}
}
}
}
struct TypeButton: View {
@EnvironmentObject var model: NCAssistantTask
let taskType: NKTextProcessingTaskType?
var scrollProxy: ScrollViewProxy
var body: some View {
Button {
model.selectTaskType(taskType)
withAnimation {
scrollProxy.scrollTo(taskType?.id, anchor: .center)
}
} label: {
Text(taskType?.name ?? "").font(.body)
}
.padding(.horizontal)
.padding(.vertical, 7)
.foregroundStyle(model.selectedType?.id == taskType?.id ? .white : .primary)
.if(model.selectedType?.id == taskType?.id) { view in
view.background(Color(NCBrandColor.shared.brandElement))
}
.if(model.selectedType?.id != taskType?.id) { view in
view.background(.ultraThinMaterial)
}
.clipShape(.capsule)
.overlay(
RoundedRectangle(cornerRadius: 20, style: RoundedCornerStyle.continuous)
.stroke(.tertiary.opacity(0.2), lineWidth: 1)
)
.id(taskType?.id)
}
}
struct TaskItem: View {
@EnvironmentObject var model: NCAssistantTask
@State var showDeleteConfirmation = false
let task: NKTextProcessingTask
var body: some View {
NavigationLink(destination: NCAssistantTaskDetail(task: task)) {
VStack(alignment: .leading) {
Text(task.input ?? "")
.lineLimit(4)
HStack {
Label(
title: {
Text(NSLocalizedString(task.statusInfo.stringKey, comment: ""))
},
icon: {
Image(systemName: task.statusInfo.imageSystemName)
.renderingMode(.original)
.font(Font.system(.body).weight(.light))
}
)
.padding(.top, 1)
.labelStyle(CustomLabelStyle())
if let completionExpectedAt = task.completionExpectedAt {
Text(NCUtility().dateDiff(.init(timeIntervalSince1970: TimeInterval(completionExpectedAt))))
.frame(maxWidth: .infinity, alignment: .trailing)
.foregroundStyle(.tertiary)
}
}
}
.swipeActions {
Button(NSLocalizedString("_delete_", comment: "")) {
showDeleteConfirmation = true
}
.tint(.red)
}
.confirmationDialog("", isPresented: $showDeleteConfirmation) {
Button(NSLocalizedString("_delete_", comment: ""), role: .destructive) {
withAnimation {
model.deleteTask(task)
}
}
}
}
}
}
private struct CustomLabelStyle: LabelStyle {
var spacing: Double = 5
func makeBody(configuration: Configuration) -> some View {
HStack(spacing: spacing) {
configuration.icon
configuration.title
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
//
// EmptyTasksView.swift
// Nextcloud
//
// Created by Milen on 16.04.24.
// Copyright © 2024 Marino Faggiana. All rights reserved.
//
import SwiftUI
struct NCAssistantEmptyView: View {
let titleKey, subtitleKey: String
var body: some View {
VStack {
Image(systemName: "sparkles")
.renderingMode(.template)
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundStyle(Color(NCBrandColor.shared.brandElement))
.font(Font.system(.body).weight(.light))
.frame(height: 100)
Text(NSLocalizedString(titleKey, comment: ""))
.font(.system(size: 22, weight: .bold))
.padding(.bottom, 5)
Text(NSLocalizedString(subtitleKey, comment: ""))
.font(.system(size: 14))
.foregroundStyle(.secondary)
}
}
}
#Preview {
NCAssistantEmptyView(titleKey: "_no_tasks_", subtitleKey: "_create_task_subtitle_")
}

Просмотреть файл

@ -0,0 +1,102 @@
//
// NCAssistantTaskDetail.swift
// Nextcloud
//
// Created by Milen on 10.04.24.
// Copyright © 2024 Marino Faggiana. All rights reserved.
//
import SwiftUI
import NextcloudKit
struct NCAssistantTaskDetail: View {
@EnvironmentObject var model: NCAssistantTask
let task: NKTextProcessingTask
var body: some View {
ZStack(alignment: .bottom) {
InputOutputScrollView(task: task)
BottomDetailsBar(task: task)
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(NSLocalizedString("_task_details_", comment: ""))
.onAppear {
model.selectTask(task)
}
}
}
#Preview {
let model = NCAssistantTask()
return NCAssistantTaskDetail(task: NKTextProcessingTask(id: 1, type: "OCP\\TextProcessing\\FreePromptTaskType", status: 1, userId: "christine", appId: "assistant", input: "", output: "", identifier: "", completionExpectedAt: 1712666412))
.environmentObject(model)
.onAppear {
model.loadDummyData()
}
}
struct InputOutputScrollView: View {
@EnvironmentObject var model: NCAssistantTask
let task: NKTextProcessingTask
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text(NSLocalizedString("_input_", comment: "")).font(.headline)
.padding(.top, 10)
Text(model.selectedTask?.input ?? "")
.frame(maxWidth: .infinity, alignment: .topLeading)
.padding()
.background(Color(NCBrandColor.shared.textColor2).opacity(0.1))
.clipShape(.rect(cornerRadius: 8))
Text(NSLocalizedString("_output_", comment: "")).font(.headline)
.padding(.top, 10)
Text(model.selectedTask?.output ?? "")
.frame(maxWidth: .infinity, alignment: .topLeading)
.padding()
.background(Color(NCBrandColor.shared.textColor2).opacity(0.1))
.clipShape(.rect(cornerRadius: 8))
}
.padding(.horizontal)
.padding(.bottom, 80)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
}
}
struct BottomDetailsBar: View {
@EnvironmentObject var model: NCAssistantTask
let task: NKTextProcessingTask
var body: some View {
VStack(spacing: 0) {
Divider()
HStack(alignment: .bottom) {
Label(
title: {
Text(NSLocalizedString(model.selectedTask?.statusInfo.stringKey ?? "", comment: ""))
}, icon: {
Image(systemName: model.selectedTask?.statusInfo.imageSystemName ?? "")
.renderingMode(.original)
.font(Font.system(.body).weight(.light))
}
)
.frame(maxWidth: .infinity, alignment: .leading)
if let completionExpectedAt = task.completionExpectedAt {
Text(NCUtility().dateDiff(.init(timeIntervalSince1970: TimeInterval(completionExpectedAt))))
.frame(maxWidth: .infinity, alignment: .trailing)
}
}
.padding()
.background(.bar)
.frame(alignment: .bottom)
}
}
}

Просмотреть файл

@ -33,6 +33,7 @@ class NCAudioRecorderViewController: UIViewController, NCAudioRecorderDelegate {
var recording: NCAudioRecorder!
var startDate: Date = Date()
var fileName: String = ""
var serverUrl = ""
let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
@IBOutlet weak var contentContainerView: UIView!
@ -54,7 +55,7 @@ class NCAudioRecorderViewController: UIViewController, NCAudioRecorderDelegate {
voiceRecordHUD.fillColor = UIColor.green
Task {
self.fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + ".m4a", account: self.appDelegate.account, serverUrl: self.appDelegate.activeServerUrl)
self.fileName = await NCNetworking.shared.createFileName(fileNameBase: NSLocalizedString("_untitled_", comment: "") + ".m4a", account: self.appDelegate.account, serverUrl: self.serverUrl)
recording = NCAudioRecorder(to: self.fileName)
recording.delegate = self
do {
@ -96,7 +97,7 @@ class NCAudioRecorderViewController: UIViewController, NCAudioRecorderDelegate {
func uploadMetadata() {
let fileNamePath = NSTemporaryDirectory() + self.fileName
let metadata = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: UUID().uuidString, serverUrl: appDelegate.activeServerUrl, urlBase: appDelegate.urlBase, url: "", contentType: "")
let metadata = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: UUID().uuidString, serverUrl: self.serverUrl, urlBase: appDelegate.urlBase, url: "", contentType: "")
metadata.session = NCNetworking.shared.sessionUploadBackground
metadata.sessionSelector = NCGlobal.shared.selectorUploadFile
metadata.status = NCGlobal.shared.metadataStatusWaitUpload

Просмотреть файл

@ -55,7 +55,7 @@ class NCBrowserWeb: UIViewController {
buttonExit.isHidden = true
} else {
self.view.bringSubviewToFront(buttonExit)
let image = NCUtility().loadImage(named: "xmark", color: .systemBlue)
let image = NCUtility().loadImage(named: "xmark", colors: [.systemBlue])
buttonExit.setImage(image, for: .normal)
}

Просмотреть файл

@ -65,7 +65,7 @@ class NCColorPicker: UIViewController {
}
}
closeButton.setImage(NCUtility().loadImage(named: "xmark", color: .label), for: .normal)
closeButton.setImage(NCUtility().loadImage(named: "xmark", colors: [NCBrandColor.shared.iconImageColor]), for: .normal)
titleLabel.text = NSLocalizedString("_select_color_", comment: "")
orangeButton.backgroundColor = .orange

Просмотреть файл

@ -351,17 +351,13 @@ extension NCManageDatabase {
}
}
@objc func setAccountAutoUploadFileName(_ fileName: String?) {
@objc func setAccountAutoUploadFileName(_ fileName: String) {
do {
let realm = try Realm()
try realm.write {
if let result = realm.objects(tableAccount.self).filter("active == true").first {
if let fileName = fileName {
result.autoUploadFileName = fileName
} else {
result.autoUploadFileName = self.getAccountAutoUploadFileName()
}
}
}
} catch let error {

Просмотреть файл

@ -26,7 +26,6 @@ import RealmSwift
import NextcloudKit
class tableAvatar: Object {
@objc dynamic var date = NSDate()
@objc dynamic var etag = ""
@objc dynamic var fileName = ""
@ -38,9 +37,7 @@ class tableAvatar: Object {
}
extension NCManageDatabase {
func addAvatar(fileName: String, etag: String) {
do {
let realm = try Realm()
try realm.write {
@ -57,7 +54,6 @@ extension NCManageDatabase {
}
func getTableAvatar(fileName: String) -> tableAvatar? {
do {
let realm = try Realm()
realm.refresh()
@ -71,7 +67,6 @@ extension NCManageDatabase {
}
func clearAllAvatarLoaded() {
do {
let realm = try Realm()
try realm.write {
@ -88,7 +83,6 @@ extension NCManageDatabase {
@discardableResult
func setAvatarLoaded(fileName: String) -> UIImage? {
let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
var image: UIImage?
@ -112,7 +106,6 @@ extension NCManageDatabase {
}
func getImageAvatarLoaded(fileName: String) -> UIImage? {
let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
do {

Просмотреть файл

@ -103,6 +103,7 @@ extension NCManageDatabase {
let external: External?
let groupfolders: GroupFolders?
let securityguard: SecurityGuard?
let assistant: Assistant?
enum CodingKeys: String, CodingKey {
case filessharing = "files_sharing"
@ -112,6 +113,7 @@ extension NCManageDatabase {
case userstatus = "user_status"
case external, groupfolders
case securityguard = "security_guard"
case assistant
}
struct FilesSharing: Codable {
@ -269,6 +271,11 @@ extension NCManageDatabase {
struct SecurityGuard: Codable {
let diagnostics: Bool?
}
struct Assistant: Codable {
let enabled: Bool?
let version: String?
}
}
}
}
@ -324,14 +331,16 @@ extension NCManageDatabase {
global.capabilityE2EEEnabled = data.capabilities.endtoendencryption?.enabled ?? false
global.capabilityE2EEApiVersion = data.capabilities.endtoendencryption?.apiversion ?? ""
global.capabilityRichdocumentsEnabled = json.ocs.data.capabilities.richdocuments?.directediting ?? false
global.capabilityRichdocumentsMimetypes.removeAll()
global.capabilityRichDocumentsEnabled = json.ocs.data.capabilities.richdocuments?.directediting ?? false
global.capabilityRichDocumentsMimetypes.removeAll()
if let mimetypes = data.capabilities.richdocuments?.mimetypes {
for mimetype in mimetypes {
global.capabilityRichdocumentsMimetypes.append(mimetype)
global.capabilityRichDocumentsMimetypes.append(mimetype)
}
}
global.capabilityAssistantEnabled = data.capabilities.assistant?.enabled ?? false
global.capabilityActivity.removeAll()
if let activities = data.capabilities.activity?.apiv2 {
for activity in activities {

Просмотреть файл

@ -50,8 +50,9 @@ extension NCManageDatabase {
func addDirectory(e2eEncrypted: Bool, favorite: Bool, ocId: String, fileId: String, etag: String? = nil, permissions: String? = nil, richWorkspace: String? = nil, serverUrl: String, account: String) {
do {
let realm = try Realm()
let result = realm.objects(tableDirectory.self).filter("account == %@ AND ocId == %@", account, ocId).first
try realm.write {
if let result = realm.objects(tableDirectory.self).filter("account == %@ AND ocId == %@", account, ocId).first {
if let result {
result.e2eEncrypted = e2eEncrypted
result.favorite = favorite
if let etag { result.etag = etag }
@ -68,7 +69,7 @@ extension NCManageDatabase {
if let richWorkspace { result.richWorkspace = richWorkspace }
result.serverUrl = serverUrl
result.account = account
realm.add(result, update: .all)
realm.add(result, update: .modified)
}
}
} catch let error {
@ -80,8 +81,11 @@ extension NCManageDatabase {
#if !EXTENSION
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.listFilesVC[serverUrl] = nil
let windowScenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
for windowScene in windowScenes {
if let mainTabBarController = windowScene.keyWindow?.rootViewController as? NCMainTabBarController {
mainTabBarController.filesServerUrl.removeValue(forKey: serverUrl)
}
}
}
#endif

Просмотреть файл

@ -104,7 +104,7 @@ extension NCManageDatabase {
}
@discardableResult
func setMetadatasSessionInWaitDownload(metadatas: [tableMetadata], session: String, selector: String) -> tableMetadata? {
func setMetadatasSessionInWaitDownload(metadatas: [tableMetadata], session: String, selector: String, sceneIdentifier: String? = nil) -> tableMetadata? {
if metadatas.isEmpty { return nil }
var metadataUpdated: tableMetadata?
@ -113,6 +113,7 @@ extension NCManageDatabase {
try realm.write {
for metadata in metadatas {
if let result = realm.objects(tableMetadata.self).filter("ocId == %@", metadata.ocId).first {
result.sceneIdentifier = sceneIdentifier
result.session = session
result.sessionError = ""
result.sessionSelector = selector
@ -120,6 +121,7 @@ extension NCManageDatabase {
result.sessionDate = Date()
metadataUpdated = tableMetadata(value: result)
} else {
metadata.sceneIdentifier = sceneIdentifier
metadata.session = session
metadata.sessionError = ""
metadata.sessionSelector = selector
@ -142,6 +144,7 @@ extension NCManageDatabase {
let realm = try Realm()
try realm.write {
for metadata in metadatas {
metadata.sceneIdentifier = nil
metadata.session = ""
metadata.sessionError = ""
metadata.sessionSelector = ""

Просмотреть файл

@ -97,6 +97,7 @@ class tableMetadata: Object, NCUserBaseUrl {
@objc dynamic var quotaAvailableBytes: Int64 = 0
@objc dynamic var resourceType = ""
@objc dynamic var richWorkspace: String?
@objc dynamic var sceneIdentifier: String?
@objc dynamic var serverUrl = ""
@objc dynamic var session = ""
@objc dynamic var sessionDate: Date?
@ -211,7 +212,7 @@ extension tableMetadata {
}
var canSetAsAvailableOffline: Bool {
return session.isEmpty && !isDocumentViewableOnly && !isDirectoryE2EE && !e2eEncrypted
return session.isEmpty && !isDirectoryE2EE && !e2eEncrypted
}
var canShare: Bool {
@ -388,14 +389,13 @@ extension NCManageDatabase {
return metadata
}
func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatasFolder: [tableMetadata], _ metadatas: [tableMetadata]) -> Void) {
func convertFilesToMetadatas(_ files: [NKFile], useFirstAsMetadataFolder: Bool, completion: @escaping (_ metadataFolder: tableMetadata, _ metadatas: [tableMetadata]) -> Void) {
var counter: Int = 0
var isDirectoryE2EE: Bool = false
let listServerUrl = ThreadSafeDictionary<String, Bool>()
var metadataFolder = tableMetadata()
var metadataFolders: [tableMetadata] = []
var metadatas: [tableMetadata] = []
for file in files {
@ -409,26 +409,23 @@ extension NCManageDatabase {
let metadata = convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
if counter == 0 && useMetadataFolder {
metadataFolder = tableMetadata.init(value: metadata)
if counter == 0 && useFirstAsMetadataFolder {
metadataFolder = tableMetadata(value: metadata)
} else {
metadatas.append(metadata)
if metadata.directory {
metadataFolders.append(metadata)
}
}
counter += 1
}
completion(metadataFolder, metadataFolders, metadatas)
completion(metadataFolder, metadatas)
}
func convertFilesToMetadatas(_ files: [NKFile], useMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatasFolder: [tableMetadata], metadatas: [tableMetadata]) {
func convertFilesToMetadatas(_ files: [NKFile], useFirstAsMetadataFolder: Bool) async -> (metadataFolder: tableMetadata, metadatas: [tableMetadata]) {
await withUnsafeContinuation({ continuation in
convertFilesToMetadatas(files, useMetadataFolder: useMetadataFolder) { metadataFolder, metadatasFolder, metadatas in
continuation.resume(returning: (metadataFolder, metadatasFolder, metadatas))
convertFilesToMetadatas(files, useFirstAsMetadataFolder: useFirstAsMetadataFolder) { metadataFolder, metadatas in
continuation.resume(returning: (metadataFolder, metadatas))
}
})
}
@ -488,7 +485,7 @@ extension NCManageDatabase {
@discardableResult
func addMetadata(_ metadata: tableMetadata) -> tableMetadata? {
let result = tableMetadata.init(value: metadata)
let result = tableMetadata(value: metadata)
do {
let realm = try Realm()
@ -499,7 +496,7 @@ extension NCManageDatabase {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)")
return nil
}
return tableMetadata.init(value: result)
return tableMetadata(value: result)
}
func addMetadatasWithoutUpdate(_ metadatas: [tableMetadata]) {
@ -634,7 +631,11 @@ extension NCManageDatabase {
result.favorite = false
}
for metadata in metadatas {
realm.add(metadata, update: .all)
if let result = realm.objects(tableMetadata.self).filter("account == %@ AND ocId == %@", account, metadata.ocId).first {
result.favorite = true
} else {
realm.add(metadata, update: .modified)
}
}
}
} catch let error {
@ -671,7 +672,7 @@ extension NCManageDatabase {
let realm = try Realm()
realm.refresh()
guard let result = realm.objects(tableMetadata.self).filter(predicate).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -684,7 +685,7 @@ extension NCManageDatabase {
let realm = try Realm()
realm.refresh()
guard let result = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -698,7 +699,7 @@ extension NCManageDatabase {
let realm = try Realm()
realm.refresh()
let results = realm.objects(tableMetadata.self).filter(predicate)
return Array(results.map { tableMetadata.init(value: $0) })
return Array(results.map { tableMetadata(value: $0) })
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -711,7 +712,7 @@ extension NCManageDatabase {
do {
let realm = try Realm()
let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
return Array(results.map { tableMetadata.init(value: $0) })
return Array(results.map { tableMetadata(value: $0) })
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -720,7 +721,6 @@ extension NCManageDatabase {
}
func getResultsMetadatas(predicate: NSPredicate, sorted: String? = nil, ascending: Bool = false) -> Results<tableMetadata>? {
do {
let realm = try Realm()
if let sorted {
@ -759,27 +759,17 @@ extension NCManageDatabase {
return nil
}
func getAdvancedMetadatas(predicate: NSPredicate, page: Int = 0, limit: Int = 0, sorted: String, ascending: Bool) -> [tableMetadata] {
func getMetadatas(predicate: NSPredicate, numItems: Int, sorted: String, ascending: Bool) -> [tableMetadata] {
var counter: Int = 0
var metadatas: [tableMetadata] = []
do {
let realm = try Realm()
realm.refresh()
let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: sorted, ascending: ascending)
if !results.isEmpty {
if page == 0 || limit == 0 {
return Array(results.map { tableMetadata.init(value: $0) })
} else {
let nFrom = (page - 1) * limit
let nTo = nFrom + (limit - 1)
for n in nFrom...nTo {
if n == results.count {
break
}
metadatas.append(tableMetadata.init(value: results[n]))
}
}
for result in results where counter < numItems {
metadatas.append(tableMetadata(value: result))
counter += 1
}
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
@ -797,7 +787,7 @@ extension NCManageDatabase {
if results.isEmpty {
return nil
} else {
return tableMetadata.init(value: results[index])
return tableMetadata(value: results[index])
}
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
@ -813,7 +803,7 @@ extension NCManageDatabase {
let realm = try Realm()
realm.refresh()
guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -827,7 +817,7 @@ extension NCManageDatabase {
let realm = try Realm()
realm.refresh()
guard let result = realm.objects(tableMetadata.self).filter("fileName == %@ AND serverUrl == %@", fileName, serverUrl).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -845,7 +835,7 @@ extension NCManageDatabase {
let realm = try Realm()
realm.refresh()
guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", ocId).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -874,7 +864,7 @@ extension NCManageDatabase {
realm.refresh()
guard let fileId = fileId else { return nil }
guard let result = realm.objects(tableMetadata.self).filter("fileId == %@", fileId).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -902,7 +892,7 @@ extension NCManageDatabase {
let realm = try Realm()
realm.refresh()
guard let result = realm.objects(tableMetadata.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", account, serverUrl, fileName).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -992,7 +982,7 @@ extension NCManageDatabase {
do {
let realm = try Realm()
guard let result = realm.objects(tableMetadata.self).filter(NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileId == %@", metadata.account, metadata.serverUrl, metadata.livePhotoFile)).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -1053,7 +1043,7 @@ extension NCManageDatabase {
realm.refresh()
guard let directory = realm.objects(tableDirectory.self).filter("account == %@ AND serverUrl == %@", account, serverUrl).first else { return nil }
guard let result = realm.objects(tableMetadata.self).filter("ocId == %@", directory.ocId).first else { return nil }
return tableMetadata.init(value: result)
return tableMetadata(value: result)
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
@ -1150,11 +1140,23 @@ extension NCManageDatabase {
do {
let realm = try Realm()
let results = realm.objects(tableMetadata.self).filter(predicate).sorted(byKeyPath: "date", ascending: false)
return ThreadSafeArray(results.map { tableMetadata.init(value: $0) })
return ThreadSafeArray(results.map { tableMetadata(value: $0) })
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)")
}
return nil
}
func getMetadata(from url: URL?) -> tableMetadata? {
guard let url,
var serverUrl = url.deletingLastPathComponent().absoluteString.removingPercentEncoding
else { return nil }
let fileName = url.lastPathComponent
if serverUrl.hasSuffix("/") {
serverUrl = String(serverUrl.dropLast())
}
return NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "serverUrl == %@ AND fileName == %@", serverUrl, fileName))
}
}

Просмотреть файл

@ -118,15 +118,11 @@ extension NCManageDatabase {
}
}
func getTrash(filePath: String, sort: String?, ascending: Bool?, account: String) -> [tableTrash] {
let sort = sort ?? "date"
let ascending = ascending ?? false
func getTrash(filePath: String, account: String) -> [tableTrash] {
do {
let realm = try Realm()
realm.refresh()
let results = realm.objects(tableTrash.self).filter("account == %@ AND filePath == %@", account, filePath).sorted(byKeyPath: sort, ascending: ascending)
let results = realm.objects(tableTrash.self).filter("account == %@ AND filePath == %@", account, filePath).sorted(byKeyPath: "date", ascending: false)
return Array(results.map { tableTrash.init(value: $0) })
} catch let error as NSError {
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access to database: \(error)")

Просмотреть файл

@ -23,7 +23,6 @@
import SwiftUI
import NextcloudKit
import PreviewSnapshots
@objc class NCHostingCapabilitiesView: NSObject {
@ -50,6 +49,7 @@ class NCCapabilitiesViewOO: ObservableObject {
@Published var capabililies: [Capability] = []
@Published var homeServer = ""
let utilityFileSystem = NCUtilityFileSystem()
let utility = NCUtility()
init() {
loadCapabilities()
@ -65,24 +65,23 @@ class NCCapabilitiesViewOO: ObservableObject {
capabililies.removeAll()
if let image = UIImage(named: "share") {
capabililies.append(Capability(text: "File sharing", image: image, resize: true, available: NCGlobal.shared.capabilityFileSharingApiEnabled))
}
if let image = UIImage(systemName: "network") {
var image = utility.loadImage(named: "person.fill.badge.plus")
capabililies.append(Capability(text: "File sharing", image: image, resize: false, available: NCGlobal.shared.capabilityFileSharingApiEnabled))
image = utility.loadImage(named: "network")
capabililies.append(Capability(text: "External site", image: image, resize: false, available: NCGlobal.shared.capabilityExternalSites))
}
if let image = UIImage(systemName: "lock") {
image = utility.loadImage(named: "lock")
capabililies.append(Capability(text: "End-to-End Encryption", image: image, resize: false, available: NCGlobal.shared.capabilityE2EEEnabled))
}
if let image = UIImage(systemName: "bolt") {
image = utility.loadImage(named: "bolt")
capabililies.append(Capability(text: "Activity", image: image, resize: false, available: !NCGlobal.shared.capabilityActivity.isEmpty))
}
if let image = UIImage(systemName: "bell") {
image = utility.loadImage(named: "bell")
capabililies.append(Capability(text: "Notification", image: image, resize: false, available: !NCGlobal.shared.capabilityNotification.isEmpty))
}
if let image = UIImage(systemName: "trash") {
image = utility.loadImage(named: "trash")
capabililies.append(Capability(text: "Deleted files", image: image, resize: false, available: NCGlobal.shared.capabilityFilesUndelete))
}
if let editors = NCManageDatabase.shared.getDirectEditingEditors(account: activeAccount.account) {
for editor in editors {
@ -94,31 +93,26 @@ class NCCapabilitiesViewOO: ObservableObject {
}
}
if let image = UIImage(systemName: "doc.text") {
capabililies.append(Capability(text: "Text", image: image, resize: false, available: textEditor))
}
if let image = UIImage(named: "onlyoffice") {
capabililies.append(Capability(text: "ONLYOFFICE", image: image, resize: true, available: onlyofficeEditors))
}
if let image = UIImage(named: "collabora") {
capabililies.append(Capability(text: "Collabora", image: image, resize: true, available: NCGlobal.shared.capabilityRichdocumentsEnabled))
}
if let image = UIImage(systemName: "moon") {
capabililies.append(Capability(text: "User Status", image: image, resize: false, available: NCGlobal.shared.capabilityUserStatusEnabled))
}
if let image = UIImage(systemName: "ellipsis.bubble") {
capabililies.append(Capability(text: "Comments", image: image, resize: false, available: NCGlobal.shared.capabilityFilesComments))
}
if let image = UIImage(systemName: "lock") {
capabililies.append(Capability(text: "Lock file", image: image, resize: false, available: !NCGlobal.shared.capabilityFilesLockVersion.isEmpty))
}
if let image = UIImage(systemName: "person.2") {
capabililies.append(Capability(text: "Group folders", image: image, resize: false, available: NCGlobal.shared.capabilityGroupfoldersEnabled))
}
if let image = UIImage(systemName: "shield") {
capabililies.append(Capability(text: "Security Guard Diagnostics", image: image, resize: false, available: NCGlobal.shared.capabilitySecurityGuardDiagnostics))
capabililies.append(Capability(text: "Text", image: utility.loadImage(named: "doc.text"), resize: false, available: textEditor))
capabililies.append(Capability(text: "ONLYOFFICE", image: utility.loadImage(named: "onlyoffice"), resize: true, available: onlyofficeEditors))
capabililies.append(Capability(text: "Collabora", image: utility.loadImage(named: "collabora"), resize: true, available: NCGlobal.shared.capabilityRichDocumentsEnabled))
capabililies.append(Capability(text: "User Status", image: utility.loadImage(named: "moon"), resize: false, available: NCGlobal.shared.capabilityUserStatusEnabled))
capabililies.append(Capability(text: "Comments", image: utility.loadImage(named: "ellipsis.bubble"), resize: false, available: NCGlobal.shared.capabilityFilesComments))
capabililies.append(Capability(text: "Lock file", image: utility.loadImage(named: "lock"), resize: false, available: !NCGlobal.shared.capabilityFilesLockVersion.isEmpty))
capabililies.append(Capability(text: "Group folders", image: utility.loadImage(named: "person.2"), resize: false, available: NCGlobal.shared.capabilityGroupfoldersEnabled))
if NCBrandOptions.shared.brand != "Nextcloud" {
capabililies.append(Capability(text: "Security Guard Diagnostics", image: utility.loadImage(named: "shield"), resize: false, available: NCGlobal.shared.capabilitySecurityGuardDiagnostics))
}
capabililies.append(Capability(text: "Assistant", image: utility.loadImage(named: "sparkles"), resize: false, available: NCGlobal.shared.capabilityAssistantEnabled))
homeServer = utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) + "/"
}
}
@ -143,7 +137,7 @@ struct NCCapabilitiesView: View {
}
}
Section {
CapabilityName(text: $capabilitiesViewOO.homeServer, image: Image(systemName: "house"), resize: false)
CapabilityName(text: $capabilitiesViewOO.homeServer, image: Image(uiImage: NCUtility().loadImage(named: "house")), resize: false)
}
}
}
@ -188,29 +182,14 @@ struct NCCapabilitiesView: View {
.foregroundColor(.green)
} else {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.foregroundColor(Color(NCBrandColor.shared.textColor2))
}
}
}
}
struct NCCapabilitiesView_Previews: PreviewProvider {
static var previews: some View {
snapshots.previews.previewLayout(.device)
}
static var snapshots: PreviewSnapshots<String> {
PreviewSnapshots(
configurations: [
.init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "")
],
configure: { _ in
NCCapabilitiesView(capabilitiesStatus: getCapabilitiesViewOOForPreview()).padding(.top, 20).frameForPreview()
})
}
}
func getCapabilitiesViewOOForPreview() -> NCCapabilitiesViewOO {
#Preview {
func getCapabilitiesViewOOForPreview() -> NCCapabilitiesViewOO {
let capabilitiesViewOO = NCCapabilitiesViewOO()
capabilitiesViewOO.capabililies = [
NCCapabilitiesViewOO.Capability(text: "Collabora", image: UIImage(named: "collabora")!, resize: true, available: true),
@ -218,4 +197,7 @@ func getCapabilitiesViewOOForPreview() -> NCCapabilitiesViewOO {
]
capabilitiesViewOO.homeServer = "https://cloud.nextcloud.com/remote.php.dav/files/marino/"
return capabilitiesViewOO
}
return NCCapabilitiesView(capabilitiesStatus: getCapabilitiesViewOOForPreview())
}

Просмотреть файл

@ -31,7 +31,7 @@ extension UIAlertController {
/// - urlBase: UrlBase object
/// - completion: If not` nil` it overrides the default behavior which shows an error using `NCContentPresenter`
/// - Returns: The presentable alert controller
static func createFolder(serverUrl: String, urlBase: NCUserBaseUrl, markE2ee: Bool = false, completion: ((_ error: NKError) -> Void)? = nil) -> UIAlertController {
static func createFolder(serverUrl: String, urlBase: NCUserBaseUrl, markE2ee: Bool = false, sceneIdentifier: String? = nil, completion: ((_ error: NKError) -> Void)? = nil) -> UIAlertController {
let alertController = UIAlertController(title: NSLocalizedString("_create_folder_", comment: ""), message: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: NSLocalizedString("_save_", comment: ""), style: .default, handler: { _ in
@ -49,7 +49,7 @@ extension UIAlertController {
}
}
} else {
NCNetworking.shared.createFolder(fileName: fileNameFolder, serverUrl: serverUrl, account: urlBase.account, urlBase: urlBase.urlBase, userId: urlBase.userId, overwrite: false, withPush: true) { error in
NCNetworking.shared.createFolder(fileName: fileNameFolder, serverUrl: serverUrl, account: urlBase.account, urlBase: urlBase.urlBase, userId: urlBase.userId, overwrite: false, withPush: true, sceneIdentifier: sceneIdentifier) { error in
if let completion = completion {
completion(error)
} else if error != .success {

Просмотреть файл

@ -0,0 +1,28 @@
//
// UIApplication+Extension.swift
// Nextcloud
//
// Created by Marino Faggiana on 25/03/24.
// Copyright © 2024 Marino Faggiana. All rights reserved.
//
import Foundation
extension UIApplication {
var firstWindow: UIWindow? {
let windowScenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
let firstActiveScene = windowScenes.first
let keyWindow = firstActiveScene?.keyWindow
return keyWindow
}
func allSceneSessionDestructionExceptFirst() {
let windowScenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
let firstActiveScene = windowScenes.first
let options = UIWindowSceneDestructionRequestOptions()
options.windowDismissalAnimation = .standard
for windowScene in windowScenes {
if windowScene == firstActiveScene { continue }
requestSceneSessionDestruction(windowScene.session, options: options, errorHandler: nil)
}
}
}

Просмотреть файл

@ -117,7 +117,7 @@ extension UIColor {
guard let components = cgColor.components, components.count > 2 else {return false}
let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
return (brightness > 0.95)
return (brightness > 0.90)
}
@objc func isTooDark() -> Bool {
@ -128,7 +128,7 @@ extension UIColor {
guard let components = cgColor.components, components.count > 2 else {return false}
let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
return (brightness < 0.05)
return (brightness < 0.10)
}
func isLight(threshold: Float = 0.7) -> Bool {

Просмотреть файл

@ -115,6 +115,10 @@ extension UIImage {
return UIImage(cgImage: newCGImage, scale: 1, orientation: .up)
}
@objc func image(color: UIColor) -> UIImage {
return image(color: color, width: self.size.width, height: self.size.height)
}
@objc func image(color: UIColor, size: CGFloat) -> UIImage {
return image(color: color, width: size, height: size)
}

Просмотреть файл

@ -32,13 +32,13 @@ extension UINavigationController {
func setNavigationBarAppearance() {
navigationBar.tintColor = .systemBlue
navigationBar.tintColor = NCBrandColor.shared.iconImageColor
let standardAppearance = UINavigationBarAppearance()
standardAppearance.configureWithDefaultBackground()
standardAppearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
standardAppearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.textColor]
standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.textColor]
navigationBar.standardAppearance = standardAppearance
let scrollEdgeAppearance = UINavigationBarAppearance()
@ -52,13 +52,13 @@ extension UINavigationController {
func setGroupAppearance() {
navigationBar.tintColor = .systemBlue
navigationBar.tintColor = NCBrandColor.shared.iconImageColor
let standardAppearance = UINavigationBarAppearance()
standardAppearance.configureWithDefaultBackground()
standardAppearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.label]
standardAppearance.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.textColor]
standardAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: NCBrandColor.shared.textColor]
standardAppearance.backgroundColor = .systemGray6
navigationBar.standardAppearance = standardAppearance

Просмотреть файл

@ -37,4 +37,17 @@ extension View {
func hiddenConditionally(isHidden: Bool) -> some View {
isHidden ? AnyView(self.hidden()) : AnyView(self)
}
/// Applies the given transform if the given condition evaluates to `true`.
/// - Parameters:
/// - condition: The condition to evaluate.
/// - transform: The transform to apply to the source `View`.
/// - Returns: Either the original `View` or the modified `View` if the condition is `true`.
@ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
self
}
}
}

Просмотреть файл

@ -33,7 +33,7 @@ class NCFavorite: NCCollectionViewCommon {
layoutKey = NCGlobal.shared.layoutViewFavorite
enableSearchBar = false
headerRichWorkspaceDisable = true
emptyImage = UIImage(named: "star.fill")?.image(color: NCBrandColor.shared.yellowFavorite, size: UIScreen.main.bounds.width)
emptyImage = utility.loadImage(named: "star.fill", colors: [NCBrandColor.shared.yellowFavorite])
emptyTitle = "_favorite_no_files_"
emptyDescription = "_tutorial_favorite_view_"
}
@ -73,7 +73,7 @@ class NCFavorite: NCCollectionViewCommon {
searchResults: self.searchResults)
}
override func reloadDataSourceNetwork() {
override func reloadDataSourceNetwork(withQueryDB: Bool = false) {
super.reloadDataSourceNetwork()
NextcloudKit.shared.listingFavorites(showHiddenFiles: NCKeychain().showHiddenFiles, options: NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)) { task in
@ -81,12 +81,12 @@ class NCFavorite: NCCollectionViewCommon {
self.collectionView.reloadData()
} completion: { account, files, _, error in
if error == .success {
NCManageDatabase.shared.convertFilesToMetadatas(files, useMetadataFolder: false) { _, _, metadatas in
NCManageDatabase.shared.convertFilesToMetadatas(files, useFirstAsMetadataFolder: false) { _, metadatas in
NCManageDatabase.shared.updateMetadatasFavorite(account: account, metadatas: metadatas)
self.reloadDataSource()
}
} else {
self.reloadDataSource(withQueryDB: false)
self.reloadDataSource(withQueryDB: withQueryDB)
}
}
}

Просмотреть файл

@ -25,7 +25,6 @@ import UIKit
import NextcloudKit
class NCFiles: NCCollectionViewCommon {
internal var isRoot: Bool = true
internal var fileNameBlink: String?
internal var fileNameOpen: String?
@ -38,7 +37,7 @@ class NCFiles: NCCollectionViewCommon {
enableSearchBar = true
headerRichWorkspaceDisable = false
headerMenuTransferView = true
emptyImage = UIImage(named: "folder")?.image(color: NCBrandColor.shared.brandElement, size: UIScreen.main.bounds.width)
emptyImage = NCImageCache.images.folder
emptyTitle = "_files_no_files_"
emptyDescription = "_no_file_pull_down_"
}
@ -54,8 +53,6 @@ class NCFiles: NCCollectionViewCommon {
self.navigationController?.popToRootViewController(animated: false)
self.serverUrl = self.utilityFileSystem.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
self.appDelegate.activeServerUrl = self.serverUrl
self.isSearchingMode = false
self.isEditMode = false
self.selectOcId.removeAll()
@ -78,7 +75,6 @@ class NCFiles: NCCollectionViewCommon {
}
override func viewWillAppear(_ animated: Bool) {
if isRoot {
serverUrl = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
titleCurrentFolder = getNavigationTitle()
@ -86,9 +82,9 @@ class NCFiles: NCCollectionViewCommon {
super.viewWillAppear(animated)
if dataSource.metadatas.isEmpty {
reloadDataSource()
reloadDataSource(withQueryDB: true)
}
reloadDataSourceNetwork()
reloadDataSourceNetwork(withQueryDB: true)
}
override func viewDidDisappear(_ animated: Bool) {
@ -139,7 +135,11 @@ class NCFiles: NCCollectionViewCommon {
}
}
override func reloadDataSourceNetwork() {
override func reloadDataSourceNetwork(withQueryDB: Bool = false) {
if UIApplication.shared.applicationState == .background {
NextcloudKit.shared.nkCommonInstance.writeLog("[DEBUG] Files not reload datasource network with the application in background")
return
}
guard !isSearchingMode else {
return networkSearch()
}
@ -172,49 +172,48 @@ class NCFiles: NCCollectionViewCommon {
if metadatasDifferentCount != 0 || metadatasModified != 0 {
self.reloadDataSource()
} else {
self.reloadDataSource(withQueryDB: false)
self.reloadDataSource(withQueryDB: withQueryDB)
}
} else {
self.reloadDataSource(withQueryDB: false)
self.reloadDataSource(withQueryDB: withQueryDB)
}
}
}
private func networkReadFolder(completion: @escaping(_ tableDirectory: tableDirectory?, _ metadatas: [tableMetadata]?, _ metadatasDifferentCount: Int, _ metadatasModified: Int, _ error: NKError) -> Void) {
var tableDirectory: tableDirectory?
NCNetworking.shared.readFile(serverUrlFileName: serverUrl) { task in
self.dataSourceTask = task
self.collectionView.reloadData()
} completion: { account, metadataFolder, error in
guard error == .success, let metadataFolder else {
} completion: { account, metadata, error in
guard error == .success, let metadata else {
return completion(nil, nil, 0, 0, error)
}
tableDirectory = NCManageDatabase.shared.setDirectory(serverUrl: self.serverUrl, richWorkspace: metadataFolder.richWorkspace, account: account)
tableDirectory = NCManageDatabase.shared.setDirectory(serverUrl: self.serverUrl, richWorkspace: metadata.richWorkspace, account: account)
// swiftlint:disable empty_string
let forceReplaceMetadatas = tableDirectory?.etag == ""
// swiftlint:enable empty_string
if tableDirectory?.etag != metadataFolder.etag || metadataFolder.e2eEncrypted {
if tableDirectory?.etag != metadata.etag || metadata.e2eEncrypted {
NCNetworking.shared.readFolder(serverUrl: self.serverUrl,
account: self.appDelegate.account,
forceReplaceMetadatas: forceReplaceMetadatas) { task in
self.dataSourceTask = task
self.collectionView.reloadData()
} completion: { _, metadataFolder, metadatas, metadatasDifferentCount, metadatasModified, error in
guard error == .success else {
} completion: { account, metadataFolder, metadatas, metadatasDifferentCount, metadatasModified, error in
guard account == self.appDelegate.account, error == .success else {
return completion(tableDirectory, nil, 0, 0, error)
}
self.metadataFolder = metadataFolder
// E2EE
if let metadataFolder = metadataFolder,
metadataFolder.e2eEncrypted,
NCKeychain().isEndToEndEnabled(account: self.appDelegate.account),
!NCNetworkingE2EE().isInUpload(account: self.appDelegate.account, serverUrl: self.serverUrl) {
let lock = NCManageDatabase.shared.getE2ETokenLock(account: self.appDelegate.account, serverUrl: self.serverUrl)
NCKeychain().isEndToEndEnabled(account: account),
!NCNetworkingE2EE().isInUpload(account: account, serverUrl: self.serverUrl) {
let lock = NCManageDatabase.shared.getE2ETokenLock(account: account, serverUrl: self.serverUrl)
NCNetworkingE2EE().getMetadata(fileId: metadataFolder.ocId, e2eToken: lock?.e2eToken) { account, version, e2eMetadata, signature, _, error in
if error == .success, let e2eMetadata = e2eMetadata {
if account == self.appDelegate.account, error == .success, let e2eMetadata = e2eMetadata {
let error = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: signature, serverUrl: self.serverUrl, account: account, urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
if error == .success {
if version == "v1", NCGlobal.shared.capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 {
@ -262,7 +261,6 @@ class NCFiles: NCCollectionViewCommon {
}
func blinkCell(fileName: String?) {
if let fileName = fileName, let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", self.appDelegate.account, self.serverUrl, fileName)) {
let (indexPath, _) = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId)
if let indexPath = indexPath {
@ -283,7 +281,6 @@ class NCFiles: NCCollectionViewCommon {
}
func openFile(fileName: String?) {
if let fileName = fileName, let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", self.appDelegate.account, self.serverUrl, fileName)) {
let (indexPath, _) = self.dataSource.getIndexPathMetadata(ocId: metadata.ocId)
if let indexPath = indexPath {

Просмотреть файл

@ -51,7 +51,7 @@ struct ButtonRounded: ButtonStyle {
configuration.label
.padding(.horizontal, 40)
.padding(.vertical, 10)
.background(disabled ? Color(UIColor.placeholderText) : Color(NCBrandColor.shared.brand))
.background(disabled ? Color(UIColor.placeholderText) : Color(NCBrandColor.shared.brandElement))
.foregroundColor(disabled ? Color(UIColor.placeholderText) : Color(NCBrandColor.shared.brandText))
.clipShape(Capsule())
.opacity(configuration.isPressed ? 0.5 : 1.0)

Просмотреть файл

@ -22,7 +22,6 @@
//
import SwiftUI
import PreviewSnapshots
struct HUDView: View {
@ -55,7 +54,7 @@ struct Blur: UIViewRepresentable {
func makeUIView(context: Context) -> UIVisualEffectView {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: style))
effectView.backgroundColor = NCBrandColor.shared.brand
effectView.backgroundColor = NCBrandColor.shared.brandElement
return effectView
}
@ -92,18 +91,6 @@ struct ContentView: View {
}
}
struct HUDView_Previews: PreviewProvider {
static var previews: some View {
snapshots.previews.previewLayout(.sizeThatFits)
}
static var snapshots: PreviewSnapshots<String> {
PreviewSnapshots(
configurations: [
.init(name: NCGlobal.shared.defaultSnapshotConfiguration, state: "")
],
configure: { _ in
ContentView().frameForPreview()
})
}
#Preview {
ContentView()
}

Просмотреть файл

@ -33,7 +33,7 @@ class NCGroupfolders: NCCollectionViewCommon {
layoutKey = NCGlobal.shared.layoutViewGroupfolders
enableSearchBar = false
headerRichWorkspaceDisable = true
emptyImage = UIImage(named: "folder_group")?.image(color: NCBrandColor.shared.brandElement, size: UIScreen.main.bounds.width)
emptyImage = utility.loadImage(named: "folder_group", colors: [NCBrandColor.shared.brandElement])
emptyTitle = "_files_no_files_"
emptyDescription = "_tutorial_groupfolders_view_"
}
@ -74,7 +74,7 @@ class NCGroupfolders: NCCollectionViewCommon {
searchResults: self.searchResults)
}
override func reloadDataSourceNetwork() {
override func reloadDataSourceNetwork(withQueryDB: Bool = false) {
super.reloadDataSourceNetwork()
let homeServerUrl = utilityFileSystem.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
@ -102,7 +102,7 @@ class NCGroupfolders: NCCollectionViewCommon {
self.reloadDataSource()
}
} else {
self.reloadDataSource(withQueryDB: false)
self.reloadDataSource(withQueryDB: withQueryDB)
}
}
}

Просмотреть файл

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "contact.svg",
"filename" : "InfoNetwork.pdf",
"idiom" : "universal"
}
],

Просмотреть файл

@ -1,26 +0,0 @@
{
"images" : [
{
"filename" : "MenuGroupByAlphabetic.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "MenuGroupByAlphabetic@3x-1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "MenuGroupByAlphabetic@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

Двоичные данные
iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/MenuGroupByAlphabetic.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 542 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.1 KiB

Двоичные данные
iOSClient/Images.xcassets/MenuGroupByAlphabetic.imageset/MenuGroupByAlphabetic@3x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Просмотреть файл

@ -1,26 +0,0 @@
{
"images" : [
{
"filename" : "MenuGroupByDate.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "MenuGroupByDate@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "MenuGroupByDate@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

Двоичные данные
iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 319 B

Двоичные данные
iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate@2x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 570 B

Двоичные данные
iOSClient/Images.xcassets/MenuGroupByDate.imageset/MenuGroupByDate@3x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 925 B

Просмотреть файл

@ -1,26 +0,0 @@
{
"images" : [
{
"filename" : "MenuGroupByFile.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "MenuGroupByFile@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "MenuGroupByFile@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

Двоичные данные
iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 639 B

Двоичные данные
iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile@2x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 729 B

Двоичные данные
iOSClient/Images.xcassets/MenuGroupByFile.imageset/MenuGroupByFile@3x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 934 B

Просмотреть файл

@ -1,26 +0,0 @@
{
"images" : [
{
"filename" : "MenuOrderByFileName.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "MenuOrderByFileName@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "MenuOrderByFileName@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

Двоичные данные
iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 459 B

Двоичные данные
iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName@2x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 841 B

Двоичные данные
iOSClient/Images.xcassets/MenuOrderByFileName.imageset/MenuOrderByFileName@3x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Просмотреть файл

@ -1,26 +0,0 @@
{
"images" : [
{
"filename" : "MenuOrdeyByDate.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "MenuOrdeyByDate@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "MenuOrdeyByDate@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

Двоичные данные
iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 464 B

Двоичные данные
iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate@2x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 724 B

Двоичные данные
iOSClient/Images.xcassets/MenuOrdeyByDate.imageset/MenuOrdeyByDate@3x.png поставляемый

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.3 KiB

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше