From 07e9cda9e027fabdfb5ccebe6ccc6952d4198f1b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 4 Feb 2026 15:14:49 +0100 Subject: [PATCH 01/20] NCSVGRenderer Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 6 + iOSClient/Extensions/UIColor+Extension.swift | 29 +++-- iOSClient/Menu/NCContextMenuMain.swift | 5 +- iOSClient/Utility/NCSVGRenderer.swift | 120 +++++++++++++++++++ 4 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 iOSClient/Utility/NCSVGRenderer.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index e0a2d32314..7a61f30c83 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -265,6 +265,8 @@ F71F6D0B2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; }; F71F6D0C2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; }; F71F6D0D2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; }; + F71FA7902F337F3300E86192 /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71FA78F2F337F3000E86192 /* NCSVGRenderer.swift */; }; + F71FA7912F337F3300E86192 /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71FA78F2F337F3000E86192 /* NCSVGRenderer.swift */; }; F722133B2D40EF9D002F7438 /* NCFilesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F722133A2D40EF8C002F7438 /* NCFilesNavigationController.swift */; }; F7226EDC1EE4089300EBECB1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7226EDB1EE4089300EBECB1 /* Main.storyboard */; }; F722F0112CFF569500065FB5 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F722F0102CFF569500065FB5 /* MainInterface.storyboard */; }; @@ -1335,6 +1337,7 @@ F71CFA662F2A07C6007A3AE9 /* NCMedia+Netwoking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMedia+Netwoking.swift"; sourceTree = ""; }; F71D2FB62E09BBD700B751CC /* NCAutoUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCAutoUploadModel.swift; sourceTree = ""; }; F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSafeArray.swift; sourceTree = ""; }; + F71FA78F2F337F3000E86192 /* NCSVGRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCSVGRenderer.swift; sourceTree = ""; }; F722133A2D40EF8C002F7438 /* NCFilesNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCFilesNavigationController.swift; sourceTree = ""; }; F7226EDB1EE4089300EBECB1 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; F722F0102CFF569500065FB5 /* MainInterface.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainInterface.storyboard; sourceTree = ""; }; @@ -2939,6 +2942,7 @@ F7A560412AE1593700BE8FD6 /* NCOperationSaveLivePhoto.swift */, F702F30725EE5D47008F8E80 /* NCPopupViewController.swift */, F707C26421A2DC5200F6181E /* NCStoreReview.swift */, + F71FA78F2F337F3000E86192 /* NCSVGRenderer.swift */, F70BFC7320E0FA7C00C67599 /* NCUtility.swift */, F711A4DB2AF92CAD00095DD8 /* NCUtility+Date.swift */, F359D8662A7D03420023F405 /* NCUtility+Exif.swift */, @@ -4243,6 +4247,7 @@ F7D4BF3C2CA2E8D800A5E746 /* TOPasscodeViewController.m in Sources */, F74AF3A5247FB6AE00AC767B /* NCUtilityFileSystem.swift in Sources */, AF1A9B6527D0CC0500F17A9E /* UIAlertController+Extension.swift in Sources */, + F71FA7912F337F3300E86192 /* NCSVGRenderer.swift in Sources */, AF22B206277B4E4C00DAB0CC /* NCCreateFormUploadConflict.swift in Sources */, F74D50362C9856D300BBBF4C /* NCCollectionViewDataSource.swift in Sources */, F7A573692E190387009C9257 /* NCShareExtensionData.swift in Sources */, @@ -4491,6 +4496,7 @@ AFA2AC8527849604008E1EA7 /* NCActivityCommentView.swift in Sources */, AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */, F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */, + F71FA7902F337F3300E86192 /* NCSVGRenderer.swift in Sources */, AA8D31552D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift in Sources */, F758B460212C56A400515F55 /* NCScan.swift in Sources */, F76882262C0DD1E7001CF441 /* NCSettingsView.swift in Sources */, diff --git a/iOSClient/Extensions/UIColor+Extension.swift b/iOSClient/Extensions/UIColor+Extension.swift index 960db2c1f6..86915a3d9c 100644 --- a/iOSClient/Extensions/UIColor+Extension.swift +++ b/iOSClient/Extensions/UIColor+Extension.swift @@ -32,7 +32,6 @@ extension UIColor { } var hexString: String { - let cgColorInRGB = cgColor.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!, intent: .defaultIntent, options: nil)! let colorRef = cgColorInRGB.components let r = colorRef?[0] ?? 0 @@ -54,8 +53,7 @@ extension UIColor { return color } - @objc convenience init?(hex: String) { - + convenience init?(hex: String) { let r, g, b, a: CGFloat if hex.hasPrefix("#") { @@ -89,11 +87,11 @@ extension UIColor { return nil } - @objc func lighter(by percentage: CGFloat = 30.0) -> UIColor? { + func lighter(by percentage: CGFloat = 30.0) -> UIColor? { return self.adjust(by: abs(percentage) ) } - @objc func darker(by percentage: CGFloat = 30.0) -> UIColor? { + func darker(by percentage: CGFloat = 30.0) -> UIColor? { return self.adjust(by: -1 * abs(percentage) ) } @@ -109,8 +107,7 @@ extension UIColor { } } - @objc func isTooLight() -> Bool { - + func isTooLight() -> Bool { var white: CGFloat = 0.0 self.getWhite(&white, alpha: nil) if white == 1 { return true } @@ -120,8 +117,7 @@ extension UIColor { return (brightness > 0.90) } - @objc func isTooDark() -> Bool { - + func isTooDark() -> Bool { var white: CGFloat = 0.0 self.getWhite(&white, alpha: nil) if white == 0 { return true } @@ -150,4 +146,19 @@ extension UIColor { rendererContext.fill(CGRect(origin: .zero, size: size)) } } + + func toCSSColor() -> String { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + + getRed(&r, green: &g, blue: &b, alpha: &a) + + if a < 1 { + return "rgba(\(Int(r * 255)),\(Int(g * 255)),\(Int(b * 255)),\(a))" + } else { + return "rgb(\(Int(r * 255)),\(Int(g * 255)),\(Int(b * 255)))" + } + } } diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index 3f41013237..aa296f9021 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -512,13 +512,12 @@ class NCContextMenuMain: NSObject { }.withRenderingMode(image.renderingMode) } - var iconImage: UIImage + var iconImage = UIImage() if let iconUrl = item.icon, let url = URL(string: metadata.urlBase + iconUrl) { let (data, _) = try await URLSession.shared.data(from: url) - let svgkImage = SVGKImage(data: data)?.uiImage.withRenderingMode(.alwaysTemplate) - iconImage = resizedRasterImage(svgkImage ?? UIImage(), to: .init(width: 23, height: 23)) + iconImage = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 23, height: 23)) } else { iconImage = UIImage() } diff --git a/iOSClient/Utility/NCSVGRenderer.swift b/iOSClient/Utility/NCSVGRenderer.swift new file mode 100644 index 0000000000..ed6307ca39 --- /dev/null +++ b/iOSClient/Utility/NCSVGRenderer.swift @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2026 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit +import WebKit + +@MainActor +final class NCSVGRenderer: NSObject, WKNavigationDelegate { + private var navigationContinuation: CheckedContinuation? + private var webView: WKWebView? + + func renderSVGToUIImage(svgData: Data, + size: CGSize, + backgroundColor: UIColor = .clear) async throws -> UIImage { + let webView = WKWebView(frame: CGRect(origin: .zero, size: size)) + self.webView = webView + + webView.navigationDelegate = self + webView.isOpaque = false + webView.backgroundColor = backgroundColor + webView.scrollView.backgroundColor = backgroundColor + webView.layer.backgroundColor = backgroundColor.cgColor + + let cssBackground = backgroundColor == .clear ? "transparent" : backgroundColor.toCSSColor() + + let html = """ + + + + + + + + + + """ + + try await loadHTMLAsync(webView: webView, html: html) + try await waitForImageReady(webView: webView) + + let config = WKSnapshotConfiguration() + config.rect = CGRect(origin: .zero, size: size) + + let image = try await takeSnapshotAsync(webView: webView, configuration: config) + return image + } + + // MARK: - Async bridges + + private func loadHTMLAsync(webView: WKWebView, html: String) async throws { + try await withCheckedThrowingContinuation { cont in + navigationContinuation = cont + webView.loadHTMLString(html, baseURL: nil) + } + } + + private func waitForImageReady(webView: WKWebView) async throws { + let js = """ + (function() { + const img = document.getElementById('svgImage'); + if (!img) return false; + return img.complete && img.naturalWidth > 0 && img.naturalHeight > 0; + })(); + """ + + for _ in 0..<60 { + let ready = try await webView.evaluateJavaScript(js) as? Bool + if ready == true { return } + try await Task.sleep(nanoseconds: 50_000_000) + } + + throw NSError(domain: "NCSVGRenderer", code: -20) + } + + private func takeSnapshotAsync(webView: WKWebView, + configuration: WKSnapshotConfiguration) async throws -> UIImage { + try await withCheckedThrowingContinuation { cont in + webView.takeSnapshot(with: configuration) { image, error in + if let image { + cont.resume(returning: image) + } else { + cont.resume(throwing: error ?? NSError(domain: "NCSVGRenderer", code: -21)) + } + } + } + } + + // MARK: - WKNavigationDelegate + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + navigationContinuation?.resume() + navigationContinuation = nil + } + + func webView(_ webView: WKWebView, + didFail navigation: WKNavigation!, + withError error: Error) { + navigationContinuation?.resume(throwing: error) + navigationContinuation = nil + } +} From e6218b0cc6207a8c616346d13bd1be733af3f665 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 4 Feb 2026 15:23:32 +0100 Subject: [PATCH 02/20] code Signed-off-by: Marino Faggiana --- iOSClient/Menu/NCContextMenuMain.swift | 1 - iOSClient/Menu/NCContextMenuProfile.swift | 37 ++++++++++++++--------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index aa296f9021..3cfb399d7e 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -7,7 +7,6 @@ import UIKit import SwiftUI import Alamofire import NextcloudKit -import SVGKit import LucidBanner /// A context menu used in ``NCCollectionViewCommon`` and ``NCMedia`` diff --git a/iOSClient/Menu/NCContextMenuProfile.swift b/iOSClient/Menu/NCContextMenuProfile.swift index 91e4f9ecc1..3cb8828c1f 100644 --- a/iOSClient/Menu/NCContextMenuProfile.swift +++ b/iOSClient/Menu/NCContextMenuProfile.swift @@ -5,7 +5,6 @@ import Foundation import UIKit import MessageUI -import SVGKit import NextcloudKit /// A context menu for user profile actions (email, talk, etc.) @@ -89,24 +88,34 @@ class NCContextMenuProfile: NSObject { // MARK: - Action Makers private func makeActionItem(from action: NKHovercard.Action) -> UIAction { - var image = utility.loadImage(named: "person", colors: [NCBrandColor.shared.iconImageColor]) - - if let url = URL(string: action.icon), - let svgSource = SVGKSourceURL.source(from: url), - let svg = SVGKImage(source: svgSource) { - image = svg.uiImage.withTintColor( - NCBrandColor.shared.iconImageColor, - renderingMode: .alwaysOriginal - ) - } - - return UIAction( + // Placeholder + let placeholder = utility.loadImage(named: "person", colors: [NCBrandColor.shared.iconImageColor]) + let uiAction = UIAction( title: action.title, - image: image, + image: placeholder, attributes: action.appId == "timezone" ? .disabled : [] ) { _ in self.handleProfileAction(action) } + + // SVG async load + render + if let url = URL(string: action.icon) { + Task { @MainActor in + do { + let data = try Data(contentsOf: url) + let image = try await NCSVGRenderer().renderSVGToUIImage( svgData: data, size: CGSize(width: 24, height: 24)) + let tinted = image.withTintColor( + NCBrandColor.shared.iconImageColor, + renderingMode: .alwaysOriginal + ) + uiAction.image = tinted + } catch { + nkLog(error: "SVG render failed: \(error)") + } + } + } + + return uiAction } // MARK: - Action Handlers From ab6f4b8e685a053a2ed39a9fcd883b3aed070d74 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 5 Feb 2026 10:09:05 +0100 Subject: [PATCH 03/20] remove lib SVG Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 55 +-------- Widget/Dashboard/DashboardData.swift | 15 +-- iOSClient/Activity/NCActivity.swift | 27 ++++- .../Activity/NCActivityTableViewCell.swift | 45 ++++---- iOSClient/Networking/NCService.swift | 57 ---------- iOSClient/Notification/NCNotification.swift | 3 +- .../Advanced/NCShareAdvancePermission.swift | 1 - iOSClient/Utility/NCSVGRenderer.swift | 41 ++++++- iOSClient/Utility/NCUtility+Image.swift | 107 +++++++++--------- .../Viewer/NCViewerMedia/NCViewerMedia.swift | 43 +++---- .../NCViewerMedia/NCViewerMediaPage.swift | 4 +- .../Viewer/NCViewerProviderContextMenu.swift | 22 ++-- 12 files changed, 188 insertions(+), 232 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 7a61f30c83..eb635dac37 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -100,8 +100,6 @@ F33918C42C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33918C32C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift */; }; F33918C72C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33918C32C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift */; }; F3391B082B4C52C5001C0C4B /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = F3391B072B4C52C5001C0C4B /* FirebaseDatabase */; }; - F3391B0C2B4C52D5001C0C4B /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = F3391B0B2B4C52D5001C0C4B /* SVGKit */; }; - F3391B102B4C52E6001C0C4B /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = F3391B0F2B4C52E6001C0C4B /* SVGKit */; }; F3391B162B4C52F6001C0C4B /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = F3391B152B4C52F6001C0C4B /* FirebaseDatabase */; }; F33D303E2D8B129600531D64 /* AutoUploadUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33D303D2D8B129600531D64 /* AutoUploadUITests.swift */; }; F33EE6E12BF4BDA500CA1A51 /* NIOSSL in Frameworks */ = {isa = PBXBuildFile; productRef = F33EE6E02BF4BDA500CA1A51 /* NIOSSL */; }; @@ -233,7 +231,6 @@ F711A4E52AF9310500095DD8 /* NCUtility+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93474B27E34120002537EE /* NCUtility+Image.swift */; }; F711A4E92AF9327600095DD8 /* UIImage+animatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = F713FEFF2472764100214AF6 /* UIImage+animatedGIF.m */; }; F711A4EB2AF9327D00095DD8 /* UIImage+animatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = F713FEFF2472764100214AF6 /* UIImage+animatedGIF.m */; }; - F711A4EF2AF932B900095DD8 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F711A4EE2AF932B900095DD8 /* SVGKitSwift */; }; F711D63128F44801003F43C8 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9739428F17131002C43E2 /* IntentHandler.swift */; }; F7132C722D085AD200B42D6A /* NCTermOfServiceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7132C6B2D085AD200B42D6A /* NCTermOfServiceModel.swift */; }; F7132C732D085AD200B42D6A /* NCTermOfServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7132C6C2D085AD200B42D6A /* NCTermOfServiceView.swift */; }; @@ -266,7 +263,8 @@ F71F6D0C2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; }; F71F6D0D2B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71F6D062B6A6A5E00F1EB15 /* ThreadSafeArray.swift */; }; F71FA7902F337F3300E86192 /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71FA78F2F337F3000E86192 /* NCSVGRenderer.swift */; }; - F71FA7912F337F3300E86192 /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71FA78F2F337F3000E86192 /* NCSVGRenderer.swift */; }; + F71FA7922F33A2E300E86192 /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71FA78F2F337F3000E86192 /* NCSVGRenderer.swift */; }; + F71FA7942F348F8100E86192 /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F71FA78F2F337F3000E86192 /* NCSVGRenderer.swift */; }; F722133B2D40EF9D002F7438 /* NCFilesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F722133A2D40EF8C002F7438 /* NCFilesNavigationController.swift */; }; F7226EDC1EE4089300EBECB1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7226EDB1EE4089300EBECB1 /* Main.storyboard */; }; F722F0112CFF569500065FB5 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F722F0102CFF569500065FB5 /* MainInterface.storyboard */; }; @@ -602,8 +600,6 @@ F7864ACF2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864AD22A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F787704F22E7019900F287A9 /* NCShareLinkCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F787704E22E7019900F287A9 /* NCShareLinkCell.xib */; }; - F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC08298BCB4A0001BB00 /* SVGKitSwift */; }; - F787AC0B298BCB540001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC0A298BCB540001BB00 /* SVGKitSwift */; }; F788ECC7263AAAFA00ADC67F /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = F788ECC6263AAAFA00ADC67F /* MarkdownKit */; }; F78A10BF29322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78A10BE29322E8A008499B8 /* NCManageDatabase+Directory.swift */; }; F78A10C029322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78A10BE29322E8A008499B8 /* NCManageDatabase+Directory.swift */; }; @@ -1850,7 +1846,6 @@ F37208B62BAB63EF006B5430 /* EasyTipView in Frameworks */, F37208BA2BAB63EF006B5430 /* TLPhotoPicker in Frameworks */, F37208AA2BAB63EE006B5430 /* MarkdownKit in Frameworks */, - F3391B102B4C52E6001C0C4B /* SVGKit in Frameworks */, F37208AE2BAB63EE006B5430 /* MarqueeLabel in Frameworks */, F37208C02BAB63F0006B5430 /* Mantis in Frameworks */, F37208C62BAB63F0006B5430 /* LRUCache in Frameworks */, @@ -1870,7 +1865,6 @@ files = ( F3F0419D2B9F7E6E00D5155F /* RealmSwift in Frameworks */, F3391B082B4C52C5001C0C4B /* FirebaseDatabase in Frameworks */, - F3391B0C2B4C52D5001C0C4B /* SVGKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1888,7 +1882,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F711A4EF2AF932B900095DD8 /* SVGKitSwift in Frameworks */, F710FC80277B7D2700AA9FBF /* RealmSwift in Frameworks */, F74C863D2AEFBFD9009A1D4A /* LRUCache in Frameworks */, F70557B92ED44E4700135623 /* LucidBanner in Frameworks */, @@ -1909,7 +1902,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F787AC0B298BCB540001BB00 /* SVGKitSwift in Frameworks */, F783034428B5142B00B84583 /* NextcloudKit in Frameworks */, F33EE6E32BF4C00700CA1A51 /* NIOSSL in Frameworks */, F7160A7D2BE931DE0034DCB3 /* RealmSwift in Frameworks */, @@ -1944,7 +1936,6 @@ F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */, F33EE6E12BF4BDA500CA1A51 /* NIOSSL in Frameworks */, F734B06628E75C0100E180D5 /* TLPhotoPicker in Frameworks */, - F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */, F760DE032AE66EA80027D78A /* KeychainAccess in Frameworks */, F3374AF62D78B01B002A38F9 /* HashTreeCollections in Frameworks */, F33EE6F02BF4C0FF00CA1A51 /* NIO in Frameworks */, @@ -3430,7 +3421,6 @@ ); name = NextcloudUITests; packageProductDependencies = ( - F3391B0F2B4C52E6001C0C4B /* SVGKit */, F3391B152B4C52F6001C0C4B /* FirebaseDatabase */, F3F0419E2B9F7E7900D5155F /* RealmSwift */, F37208A32BAB63EE006B5430 /* QRCodeReader */, @@ -3473,7 +3463,6 @@ name = NextcloudIntegrationTests; packageProductDependencies = ( F3391B072B4C52C5001C0C4B /* FirebaseDatabase */, - F3391B0B2B4C52D5001C0C4B /* SVGKit */, F3F0419C2B9F7E6E00D5155F /* RealmSwift */, ); productName = NextcloudIntegrationTests; @@ -3528,7 +3517,6 @@ F7A560472AE15D5000BE8FD6 /* Queuer */, F760DE082AE66ED00027D78A /* KeychainAccess */, F74C863C2AEFBFD9009A1D4A /* LRUCache */, - F711A4EE2AF932B900095DD8 /* SVGKitSwift */, F33EE6E62BF4C02600CA1A51 /* NIOSSL */, F7B8F6172EAB7516006A70D6 /* JDStatusBarNotification */, F70557B82ED44E4700135623 /* LucidBanner */, @@ -3554,7 +3542,6 @@ packageProductDependencies = ( F783030C28B4C59A00B84583 /* SwiftEntryKit */, F783034328B5142B00B84583 /* NextcloudKit */, - F787AC0A298BCB540001BB00 /* SVGKitSwift */, F7A560452AE15D3D00BE8FD6 /* Queuer */, F760DE042AE66EBE0027D78A /* KeychainAccess */, F7160A7C2BE931DE0034DCB3 /* RealmSwift */, @@ -3626,7 +3613,6 @@ F77333872927A72100466E35 /* OpenSSL */, F77BC3EA293E5268005F2B08 /* Swifter */, F7D56B192972405500FA46C4 /* Mantis */, - F787AC08298BCB4A0001BB00 /* SVGKitSwift */, F7A1050D29E587AF00FFD92B /* TagListView */, F7F623B42A5EF4D30022D3D4 /* Gzip */, F76B649D2ADFFDEC00014640 /* LRUCache */, @@ -3798,7 +3784,6 @@ ); mainGroup = F7F67B9F1A24D27800EE80DA; packageReferences = ( - F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */, F7ED547A25EEA65400956C55 /* XCRemoteSwiftPackageReference "QRCodeReader" */, F72DA9B225F53E4E00B87DB1 /* XCRemoteSwiftPackageReference "SwiftRichString" */, F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */, @@ -4247,7 +4232,6 @@ F7D4BF3C2CA2E8D800A5E746 /* TOPasscodeViewController.m in Sources */, F74AF3A5247FB6AE00AC767B /* NCUtilityFileSystem.swift in Sources */, AF1A9B6527D0CC0500F17A9E /* UIAlertController+Extension.swift in Sources */, - F71FA7912F337F3300E86192 /* NCSVGRenderer.swift in Sources */, AF22B206277B4E4C00DAB0CC /* NCCreateFormUploadConflict.swift in Sources */, F74D50362C9856D300BBBF4C /* NCCollectionViewDataSource.swift in Sources */, F7A573692E190387009C9257 /* NCShareExtensionData.swift in Sources */, @@ -4260,6 +4244,7 @@ F799DF832C4B7DCC003410B5 /* NCSectionFooter.swift in Sources */, AF22B218277D196700DAB0CC /* NCShareExtension+Files.swift in Sources */, F799DF862C4B7E56003410B5 /* NCSectionHeader.swift in Sources */, + F71FA7942F348F8100E86192 /* NCSVGRenderer.swift in Sources */, F702F2D025EE5B5C008F8E80 /* NCGlobal.swift in Sources */, F72437802C10B92400C7C68D /* NCSharePermissions.swift in Sources */, F7EDE4DB262D7BA200414FE6 /* NCCellProtocol.swift in Sources */, @@ -4350,6 +4335,7 @@ F724377C2C10B92200C7C68D /* NCSharePermissions.swift in Sources */, F77ED59328C9CEA000E24ED0 /* ToolbarWidgetProvider.swift in Sources */, F72A17D828B221E300F3F159 /* DashboardWidgetView.swift in Sources */, + F71FA7922F33A2E300E86192 /* NCSVGRenderer.swift in Sources */, F77ED59528C9CEA400E24ED0 /* ToolbarWidgetView.swift in Sources */, F78302FB28B4C3EE00B84583 /* NCManageDatabase+Video.swift in Sources */, F7C687EA2D22BDE5004757BC /* NCManageDatabase+RecommendedFiles.swift in Sources */, @@ -6060,14 +6046,6 @@ kind = branch; }; }; - F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SVGKit/SVGKit.git"; - requirement = { - kind = upToNextMinorVersion; - minimumVersion = 3.0.0; - }; - }; F75EAED626D2552E00F4320E /* XCRemoteSwiftPackageReference "MarqueeLabel" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/cbpowell/MarqueeLabel"; @@ -6229,16 +6207,6 @@ package = F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseDatabase; }; - F3391B0B2B4C52D5001C0C4B /* SVGKit */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKit; - }; - F3391B0F2B4C52E6001C0C4B /* SVGKit */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKit; - }; F3391B152B4C52F6001C0C4B /* FirebaseDatabase */ = { isa = XCSwiftPackageProductDependency; package = F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; @@ -6419,11 +6387,6 @@ package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; - F711A4EE2AF932B900095DD8 /* SVGKitSwift */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKitSwift; - }; F7160A7C2BE931DE0034DCB3 /* RealmSwift */ = { isa = XCSwiftPackageProductDependency; package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; @@ -6579,16 +6542,6 @@ package = F783034028B511D200B84583 /* XCRemoteSwiftPackageReference "NextcloudKit" */; productName = NextcloudKit; }; - F787AC08298BCB4A0001BB00 /* SVGKitSwift */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKitSwift; - }; - F787AC0A298BCB540001BB00 /* SVGKitSwift */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKitSwift; - }; F788ECC6263AAAFA00ADC67F /* MarkdownKit */ = { isa = XCSwiftPackageProductDependency; package = F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */; diff --git a/Widget/Dashboard/DashboardData.swift b/Widget/Dashboard/DashboardData.swift index c336b393e1..07ec146064 100644 --- a/Widget/Dashboard/DashboardData.swift +++ b/Widget/Dashboard/DashboardData.swift @@ -26,7 +26,6 @@ import WidgetKit import Intents import NextcloudKit import RealmSwift -import SVGKit struct DashboardDataEntry: TimelineEntry { let date: Date @@ -76,7 +75,7 @@ func getDashboardItems(displaySize: CGSize, withButton: Bool) -> Int { } } -func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?) -> UIImage? { +func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?) async -> UIImage? { guard let data = data else { return nil } @@ -85,12 +84,14 @@ func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?) -> if let image = UIImage(data: data), let image = image.resizeImage(size: size) { imageData = image - } else if let image = SVGKImage(data: data) { - image.size = size - imageData = image.uiImage } else { - print("error") + do { + imageData = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: size) + } catch { + print("Unsupported image format: \(error.localizedDescription)") + } } + if let fileName = fileNameToWrite, let image = imageData { do { let fileNamePath: String = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName + ".png") @@ -229,7 +230,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis let (_, _, error) = await NextcloudKit.shared.downloadPreviewAsync(url: url, account: activeTableAccount.account) if error == .success, let data = responseData?.data, - let image = convertDataToImage(data: data, size: NCGlobal.shared.size256, fileNameToWrite: fileName) { + let image = await convertDataToImage(data: data, size: NCGlobal.shared.size256, fileNameToWrite: fileName) { icon = image } } diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index dd740a534f..f45e7e3bf4 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -5,7 +5,6 @@ import UIKit import SwiftRichString import NextcloudKit -import SVGKit class NCActivity: UIViewController, NCSharePagingContent { @IBOutlet weak var viewContainerConstraint: NSLayoutConstraint! @@ -278,11 +277,27 @@ extension NCActivity: UITableViewDataSource { let fileNameIcon = (activity.icon as NSString).lastPathComponent let fileNameLocalPath = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNameIcon) - if FileManager.default.fileExists(atPath: fileNameLocalPath) { - let image = fileNameIcon.contains(".svg") ? SVGKImage(contentsOfFile: fileNameLocalPath)?.uiImage : UIImage(contentsOfFile: fileNameLocalPath) - - if let image { - cell.icon.image = image.withTintColor(NCBrandColor.shared.textColor, renderingMode: .alwaysOriginal) + if FileManager.default.fileExists(atPath: fileNameLocalPath), + fileNameIcon.contains(".svg") { + Task { + do { + let url = URL(fileURLWithPath: fileNameLocalPath) + let data = try Data(contentsOf: url) + if let image = UIImage(data: data) { + cell.icon.image = image + } else { + let image = try await NCSVGRenderer().renderSVGToUIImage( + svgData: data, + size: CGSize(width: 24, height: 24), + backgroundColor: .clear + ) + if cell.idActivity == activity.idActivity { + cell.icon.image = image + } + } + } catch { + print("SVG render failed: \(error)") + } } } else { NextcloudKit.shared.downloadContent(serverUrl: activity.icon, account: activity.account) { task in diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index 627a11e8fa..def75dcb91 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -10,7 +10,7 @@ import Queuer class NCActivityCollectionViewCell: UICollectionViewCell { @IBOutlet weak var imageView: UIImageView! - var fileId = "" + var fileId: Int = 0 var indexPath = IndexPath() } @@ -30,7 +30,6 @@ class NCActivityTableViewCell: UITableViewCell, NCCellProtocol { var viewController = NCActivity() let utilityFileSystem = NCUtilityFileSystem() var account: String! - let utility = NCUtility() var indexPath: IndexPath { @@ -130,37 +129,37 @@ extension NCActivityTableViewCell: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell: NCActivityCollectionViewCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as? NCActivityCollectionViewCell)! + let activityPreview = activityPreviews[indexPath.row] cell.imageView.image = nil cell.indexPath = indexPath - - let activityPreview = activityPreviews[indexPath.row] - let fileId = String(activityPreview.fileId) + cell.imageView.image = NCImageCache.shared.getImageFile() + cell.fileId = activityPreview.fileId // Trashbin if activityPreview.view == "trashbin" { let source = activityPreview.source - - utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 100, rewrite: false, account: account, id: idActivity) { imageNamePath, id in - if let imageNamePath = imageNamePath, id == self.idActivity, let image = UIImage(contentsOfFile: imageNamePath) { + Task { + let results = await utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, size: 100, rewrite: false, account: account, id: idActivity) + if let image = results.image, + let idActivity = results.id, + cell.fileId == activityPreview.fileId { cell.imageView.image = image - } else { - cell.imageView.image = NCImageCache.shared.getImageFile() } } } else { if activityPreview.isMimeTypeIcon { let source = activityPreview.source - - utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, width: 150, rewrite: false, account: account, id: idActivity) { imageNamePath, id in - if let imageNamePath = imageNamePath, id == self.idActivity, let image = UIImage(contentsOfFile: imageNamePath) { + Task { + let results = await utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, size: 150, rewrite: false, account: account, id: idActivity) + if let image = results.image, + let idActivity = results.id, + cell.fileId == activityPreview.fileId { cell.imageView.image = image - } else { - cell.imageView.image = NCImageCache.shared.getImageFile() } } } else { - if let activitySubjectRich = NCManageDatabase.shared.getActivitySubjectRich(account: activityPreview.account, idActivity: idActivity, id: fileId) { + if let activitySubjectRich = NCManageDatabase.shared.getActivitySubjectRich(account: activityPreview.account, idActivity: idActivity, id: String(activityPreview.fileId)) { let fileNamePath = NCUtilityFileSystem().createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: activitySubjectRich.name) if FileManager.default.fileExists(atPath: fileNamePath), let image = UIImage(contentsOfFile: fileNamePath) { @@ -169,10 +168,10 @@ extension NCActivityTableViewCell: UICollectionViewDataSource { } else { cell.imageView?.image = utility.loadImage(named: "doc", colors: [NCBrandColor.shared.iconImageColor]) cell.imageView?.contentMode = .scaleAspectFit - cell.fileId = fileId + cell.fileId = activityPreview.fileId if !FileManager.default.fileExists(atPath: fileNamePath) { - if NCNetworking.shared.downloadThumbnailActivityQueue.operations.filter({ ($0 as? NCOperationDownloadThumbnailActivity)?.fileId == fileId }).isEmpty { - NCNetworking.shared.downloadThumbnailActivityQueue.addOperation(NCOperationDownloadThumbnailActivity(fileId: fileId, etag: "", fileNamePreviewLocalPath: fileNamePath, account: account, collectionView: collectionView)) + if NCNetworking.shared.downloadThumbnailActivityQueue.operations.filter({ ($0 as? NCOperationDownloadThumbnailActivity)?.fileId == activityPreview.fileId }).isEmpty { + NCNetworking.shared.downloadThumbnailActivityQueue.addOperation(NCOperationDownloadThumbnailActivity(fileId: activityPreview.fileId, etag: "", fileNamePreviewLocalPath: fileNamePath, account: account, collectionView: collectionView)) } } } @@ -200,11 +199,11 @@ extension NCActivityTableViewCell: UICollectionViewDelegateFlowLayout { class NCOperationDownloadThumbnailActivity: ConcurrentOperation, @unchecked Sendable { var collectionView: UICollectionView? var fileNamePreviewLocalPath: String - var fileId: String + var fileId: Int var account: String var etag: String - init(fileId: String, etag: String, fileNamePreviewLocalPath: String, account: String, collectionView: UICollectionView?) { + init(fileId: Int, etag: String, fileNamePreviewLocalPath: String, account: String, collectionView: UICollectionView?) { self.fileNamePreviewLocalPath = fileNamePreviewLocalPath self.fileId = fileId self.account = account @@ -214,10 +213,10 @@ class NCOperationDownloadThumbnailActivity: ConcurrentOperation, @unchecked Send override func start() { guard !isCancelled else { return self.finish() } - NextcloudKit.shared.downloadPreview(fileId: fileId, etag: etag, account: account) { task in + NextcloudKit.shared.downloadPreview(fileId: String(fileId), etag: etag, account: account) { task in Task { let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.account, - path: self.fileId, + path: String(self.fileId), name: "DownloadPreview") await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) } diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index 911e11b57d..1b52e8f933 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -5,7 +5,6 @@ import UIKit @preconcurrency import NextcloudKit import RealmSwift -import SVGKit class NCService: NSObject { let utilityFileSystem = NCUtilityFileSystem() @@ -42,9 +41,6 @@ class NCService: NSObject { // Start a background synchronization task await synchronize(account: account) - - // Fetch and display dashboard widgets if available - await requestDashboardWidget(account: account) } } @@ -265,59 +261,6 @@ class NCService: NSObject { // MARK: - - private func requestDashboardWidget(account: String) async { - let results = await NextcloudKit.shared.getDashboardWidgetAsync(account: account, taskHandler: { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, - name: "getDashboardWidget") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - }) - if results.error == .success, - let dashboardWidgets = results.dashboardWidgets { - await NCManageDatabase.shared.addDashboardWidgetAsync(account: account, dashboardWidgets: dashboardWidgets) - for widget in dashboardWidgets { - if let url = URL(string: widget.iconUrl), - let fileName = widget.iconClass { - let results = await NextcloudKit.shared.downloadPreviewAsync(url: url, account: account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, - path: url.absoluteString, - name: "DownloadPreview") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } - if results.error == .success, - let data = results.responseData?.data { - await MainActor.run { - let size = CGSize(width: 256, height: 256) - let finalImage: UIImage? - if let uiImage = UIImage(data: data)?.resizeImage(size: size) { - finalImage = uiImage - } else if let svgImage = SVGKImage(data: data) { - svgImage.size = size - finalImage = svgImage.uiImage - } else { - print("Unsupported image format") - finalImage = nil - } - if let image = finalImage { - let filePath = (self.utilityFileSystem.directoryUserData as NSString).appendingPathComponent(fileName + ".png") - do { - try image.pngData()?.write(to: URL(fileURLWithPath: filePath), options: .atomic) - } catch { - print("Failed to write image to disk: \(error.localizedDescription)") - } - } - } - } - } - } - } - } - - // MARK: - - func sendClientDiagnosticsRemoteOperation(account: String) async { let capabilities = await NKCapabilities.shared.getCapabilities(for: account) guard capabilities.securityGuardDiagnostics, diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index c1cd0be3e2..a6824723aa 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -340,7 +340,8 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { let sortedNotifications = notifications.sorted { $0.date > $1.date } for notification in sortedNotifications { if let icon = notification.icon { - self.utility.convertSVGtoPNGWriteToUserData(svgUrlString: icon, width: 25, rewrite: false, account: session.account) { _, _ in + let results = await self.utility.convertSVGtoPNGWriteToUserData(svgUrlString: icon, size: 25, rewrite: false, account: session.account) + if results.image != nil { self.tableView.reloadData() } } diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift index 832a3ece53..d84ddf3a79 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift @@ -23,7 +23,6 @@ import UIKit import NextcloudKit -import SVGKit import CloudKit class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDelegate, NCShareNavigationTitleSetting { diff --git a/iOSClient/Utility/NCSVGRenderer.swift b/iOSClient/Utility/NCSVGRenderer.swift index ed6307ca39..02d818a42c 100644 --- a/iOSClient/Utility/NCSVGRenderer.swift +++ b/iOSClient/Utility/NCSVGRenderer.swift @@ -9,10 +9,25 @@ import WebKit final class NCSVGRenderer: NSObject, WKNavigationDelegate { private var navigationContinuation: CheckedContinuation? private var webView: WKWebView? + private let utilityFileSystem = NCUtilityFileSystem() func renderSVGToUIImage(svgData: Data, size: CGSize, + fileName: String, backgroundColor: UIColor = .clear) async throws -> UIImage { + // try to get from directoryUserData + let fileName = fileName.replacingOccurrences(of: ".svg", with: ".png") + let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) + if FileManager.default.fileExists(atPath: path) { + do { + let url = URL(fileURLWithPath: path) + let data = try Data(contentsOf: url) + if let image = UIImage(data: data) { + return image + } + } catch { } + } + let webView = WKWebView(frame: CGRect(origin: .zero, size: size)) self.webView = webView @@ -61,10 +76,28 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { config.rect = CGRect(origin: .zero, size: size) let image = try await takeSnapshotAsync(webView: webView, configuration: config) + if let data = image.pngData() { + try data.write(to: URL(fileURLWithPath: path)) + } + return image } - // MARK: - Async bridges + func xx(path: String) async throws -> UIImage? { + guard FileManager.default.fileExists(atPath: path) else { + return nil + } + + do { + let url = URL(fileURLWithPath: path) + let data = try Data(contentsOf: url) + let image = UIImage(data: data) + return image + } catch { + print("SVG render failed: \(error)") + return nil + } + } private func loadHTMLAsync(webView: WKWebView, html: String) async throws { try await withCheckedThrowingContinuation { cont in @@ -82,17 +115,15 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { })(); """ + // wait max 3 sec. for _ in 0..<60 { let ready = try await webView.evaluateJavaScript(js) as? Bool if ready == true { return } try await Task.sleep(nanoseconds: 50_000_000) } - - throw NSError(domain: "NCSVGRenderer", code: -20) } - private func takeSnapshotAsync(webView: WKWebView, - configuration: WKSnapshotConfiguration) async throws -> UIImage { + private func takeSnapshotAsync(webView: WKWebView, configuration: WKSnapshotConfiguration) async throws -> UIImage { try await withCheckedThrowingContinuation { cont in webView.takeSnapshot(with: configuration) { image, error in if let image { diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index c06468f8a2..7c39f3148c 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -9,7 +9,6 @@ import PDFKit import Accelerate import CoreMedia import Photos -import SVGKit extension NCUtility { func loadImage(named imageName: String, colors: [UIColor]? = nil, size: CGFloat? = nil, useTypeIconFile: Bool = false, account: String? = nil) -> UIImage { @@ -261,77 +260,79 @@ extension NCUtility { return avatarImage } - func convertSVGtoPNGWriteToUserData(svgUrlString: String, fileName: String? = nil, width: CGFloat? = nil, rewrite: Bool, account: String, id: Int? = nil, completion: @escaping (_ imageNamePath: String?, _ id: Int?) -> Void) { + func convertSVGtoPNGWriteToUserData(svgUrlString: String, + fileName: String? = nil, + size: CGFloat, + rewrite: Bool, + account: String, + id: Int? = nil) async -> (image: UIImage?, id: Int?) { var fileNamePNG = "" guard let svgUrlString = svgUrlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let iconURL = URL(string: svgUrlString) else { - return completion(nil, id) + return (nil, id) } - if let fileName = fileName { + if let fileName { fileNamePNG = fileName } else { fileNamePNG = iconURL.deletingPathExtension().lastPathComponent + ".png" } - let imageNamePath = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNamePNG) + let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNamePNG) - if !FileManager.default.fileExists(atPath: imageNamePath) || rewrite == true { - NextcloudKit.shared.downloadContent(serverUrl: iconURL.absoluteString, account: account) { task in + if !FileManager.default.fileExists(atPath: path) || rewrite { + let results = await NextcloudKit.shared.downloadContentAsync(serverUrl: iconURL.absoluteString, account: account) { task in Task { let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, path: iconURL.absoluteString, name: "downloadContent") await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) } - } completion: { _, responseData, error in - if error == .success, let data = responseData?.data { - if let image = UIImage(data: data) { - var newImage: UIImage = image - - if width != nil { - - let ratio = image.size.height / image.size.width - let newSize = CGSize(width: width!, height: width! * ratio) - - let renderFormat = UIGraphicsImageRendererFormat.default() - renderFormat.opaque = false - let renderer = UIGraphicsImageRenderer(size: CGSize(width: newSize.width, height: newSize.height), format: renderFormat) - newImage = renderer.image { _ in - image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) - } - } - guard let pngImageData = newImage.pngData() else { - return completion(nil, id) - } - try? pngImageData.write(to: URL(fileURLWithPath: imageNamePath)) - - return completion(imageNamePath, id) - } else { - guard let svgImage: SVGKImage = SVGKImage(data: data) else { - return completion(nil, id) - } - - if width != nil { - let scale = svgImage.size.height / svgImage.size.width - svgImage.size = CGSize(width: width!, height: width! * scale) - } - guard let image: UIImage = svgImage.uiImage else { - return completion(nil, id) - } - guard let pngImageData = image.pngData() else { - return completion(nil, id) - } - - try? pngImageData.write(to: URL(fileURLWithPath: imageNamePath)) - - return completion(imageNamePath, id) - } - } else { - return completion(nil, id) + } + guard results.error == .success, + let data = results.responseData?.data else { + return(nil, id) + } + + // is an image + if let image = UIImage(data: data) { + let ratio = image.size.height / image.size.width + let size = CGSize(width: size, height: size * ratio) + let renderFormat = UIGraphicsImageRendererFormat.default() + renderFormat.opaque = false + let renderer = UIGraphicsImageRenderer(size: CGSize(width: size.width, height: size.height), format: renderFormat) + let newImage = renderer.image { _ in + image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) + } + guard let pngImageData = newImage.pngData() else { + return(nil, id) + } + + do { + try pngImageData.write(to: URL(fileURLWithPath: path)) + return(newImage, id) + } catch { + return(nil, id) } } + // is a SVG + do { + let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: CGSize(width: size, height: size)) + guard let pngImageData = image.pngData() else { + return(nil, id) + } + try pngImageData.write(to: URL(fileURLWithPath: path)) + return(image, id) + } catch { + return(nil, id) + } } else { - return completion(imageNamePath, id) + do { + let url = URL(fileURLWithPath: path) + let data = try Data(contentsOf: url) + return(UIImage(data: data), id) + } catch { + return(nil, id) + } } } diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index 2068227682..d876849486 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later import UIKit -import SVGKit import NextcloudKit import EasyTipView import SwiftUI @@ -108,7 +107,9 @@ class NCViewerMedia: UIViewController { self.image = nil self.imageVideoContainer.image = nil - loadImage() + Task {@MainActor in + await loadImage() + } } override func viewWillAppear(_ animated: Bool) { @@ -125,7 +126,9 @@ class NCViewerMedia: UIViewController { if metadata.isImage, let viewerMediaPage = self.viewerMediaPage { if viewerMediaPage.modifiedOcId.contains(metadata.ocId) { viewerMediaPage.modifiedOcId.removeAll(where: { $0 == metadata.ocId }) - loadImage() + Task {@MainActor in + await loadImage() + } } } } @@ -246,7 +249,7 @@ class NCViewerMedia: UIViewController { // MARK: - Image - func loadImage() { + func loadImage() async { guard let metadata = self.database.getMetadataFromOcId(metadata.ocId) else { return } self.metadata = metadata let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, @@ -299,23 +302,25 @@ class NCViewerMedia: UIViewController { } return } else if fileNameExtension == "SVG" { - if let svgImage = SVGKImage(contentsOfFile: fileNamePath) { - svgImage.size = global.size1024 - if let image = svgImage.uiImage { - if !NCUtility().existsImage(ocId: metadata.ocId, - etag: metadata.etag, - ext: global.previewExt1024, - userId: metadata.userId, - urlBase: metadata.urlBase), let data = image.jpegData(compressionQuality: 1.0) { - utility.createImageFileFrom(data: data, metadata: metadata) - } - self.image = image - self.imageVideoContainer.image = self.image - return + do { + let url = URL(fileURLWithPath: fileNamePath) + let data = try Data(contentsOf: url) + let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 1024, height: 1024)) + if !NCUtility().existsImage(ocId: metadata.ocId, + etag: metadata.etag, + ext: global.previewExt1024, + userId: metadata.userId, + urlBase: metadata.urlBase), let data = image.jpegData(compressionQuality: 1.0) { + utility.createImageFileFrom(data: data, metadata: metadata) } + self.image = image + self.imageVideoContainer.image = self.image + return + } catch { + print("Unsupported image format: \(error.localizedDescription)") + self.image = self.utility.loadImage(named: "photo", colors: [NCBrandColor.shared.iconImageColor2]) + self.imageVideoContainer.image = self.image } - self.image = self.utility.loadImage(named: "photo", colors: [NCBrandColor.shared.iconImageColor2]) - self.imageVideoContainer.image = self.image return } else if let image = UIImage(contentsOfFile: fileNamePath) { self.image = image diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift index 25ac5effc8..87d9547771 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift @@ -615,13 +615,13 @@ extension NCViewerMediaPage: NCTransferDelegate { ncplayer.openAVPlayer(url: url) } } else if metadata.isImage { - self.currentViewController.loadImage() + await self.currentViewController.loadImage() } // UPLOAD case self.global.networkingStatusUploaded: guard error == .success else { return } if self.currentViewController.metadata.ocId == ocId { - self.currentViewController.loadImage() + await self.currentViewController.loadImage() } else { self.modifiedOcId.append(ocId) } diff --git a/iOSClient/Viewer/NCViewerProviderContextMenu.swift b/iOSClient/Viewer/NCViewerProviderContextMenu.swift index 638da59a3c..219dd590f3 100644 --- a/iOSClient/Viewer/NCViewerProviderContextMenu.swift +++ b/iOSClient/Viewer/NCViewerProviderContextMenu.swift @@ -4,7 +4,6 @@ import UIKit import NextcloudKit -import SVGKit import MobileVLCKit class NCViewerProviderContextMenu: UIViewController { @@ -47,7 +46,9 @@ class NCViewerProviderContextMenu: UIViewController { } // VIEW IMAGE if metadata.isImage && utilityFileSystem.fileProviderStorageExists(metadata) { - viewImage(metadata: metadata) + Task {@MainActor in + await viewImage(metadata: metadata) + } } // VIEW LIVE PHOTO if let metadataLivePhoto = metadataLivePhoto, utilityFileSystem.fileProviderStorageExists(metadataLivePhoto) { @@ -144,7 +145,7 @@ class NCViewerProviderContextMenu: UIViewController { // MARK: - Viewer - private func viewImage(metadata: tableMetadata) { + private func viewImage(metadata: tableMetadata) async { var image: UIImage? let filePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileName: metadata.fileNameView, @@ -158,9 +159,16 @@ class NCViewerProviderContextMenu: UIViewController { fileName: metadata.fileNameView, userId: metadata.userId, urlBase: metadata.urlBase) - if let svgImage = SVGKImage(contentsOfFile: imagePath) { - svgImage.size = NCGlobal.shared.size1024 - image = svgImage.uiImage + do { + let url = URL(fileURLWithPath: imagePath) + let data = try Data(contentsOf: url) + image = try await NCSVGRenderer().renderSVGToUIImage( + svgData: data, + size: CGSize(width: 1024, height: 1024), + backgroundColor: .clear + ) + } catch { + print("SVG render error: \(error.localizedDescription)") } } else { image = UIImage(contentsOfFile: filePath) @@ -302,7 +310,7 @@ extension NCViewerProviderContextMenu: NCTransferDelegate { ocId == self.metadata?.ocId, let metadata = await NCManageDatabase.shared.getMetadataFromOcIdAsync(ocId) { if metadata.isImage { - self.viewImage(metadata: metadata) + await self.viewImage(metadata: metadata) } else if metadata.isVideo || metadata.isAudio { self.viewVideo(metadata: metadata) } From 2a04685599158bd47da417a3547a85741f768f91 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 5 Feb 2026 11:00:13 +0100 Subject: [PATCH 04/20] code Signed-off-by: Marino Faggiana --- Widget/Dashboard/DashboardData.swift | 8 +++-- .../Activity/NCActivityTableViewCell.swift | 4 +-- iOSClient/Menu/NCContextMenuMain.swift | 5 ++-- iOSClient/Notification/NCNotification.swift | 5 +++- iOSClient/Utility/NCSVGRenderer.swift | 30 +++++++++++-------- iOSClient/Utility/NCUtility+Image.swift | 26 +++++++--------- iOSClient/Utility/NCUtilityFileSystem.swift | 7 +++++ 7 files changed, 49 insertions(+), 36 deletions(-) diff --git a/Widget/Dashboard/DashboardData.swift b/Widget/Dashboard/DashboardData.swift index 07ec146064..e688a7cc2a 100644 --- a/Widget/Dashboard/DashboardData.swift +++ b/Widget/Dashboard/DashboardData.swift @@ -75,7 +75,7 @@ func getDashboardItems(displaySize: CGSize, withButton: Bool) -> Int { } } -func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?) async -> UIImage? { +func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?, user: String) async -> UIImage? { guard let data = data else { return nil } @@ -86,7 +86,9 @@ func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?) asy imageData = image } else { do { - imageData = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: size) + imageData = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, + size: size, + fileName: fileNameToWrite) } catch { print("Unsupported image format: \(error.localizedDescription)") } @@ -230,7 +232,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis let (_, _, error) = await NextcloudKit.shared.downloadPreviewAsync(url: url, account: activeTableAccount.account) if error == .success, let data = responseData?.data, - let image = await convertDataToImage(data: data, size: NCGlobal.shared.size256, fileNameToWrite: fileName) { + let image = await convertDataToImage(data: data, size: NCGlobal.shared.size256, fileNameToWrite: fileName, user: activeTableAccount.userId) { icon = image } } diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index def75dcb91..ff36d38da5 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -140,7 +140,7 @@ extension NCActivityTableViewCell: UICollectionViewDataSource { if activityPreview.view == "trashbin" { let source = activityPreview.source Task { - let results = await utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, size: 100, rewrite: false, account: account, id: idActivity) + let results = await utility.convertSVGtoPNGWriteToUserData(fileName: source, size: 100, rewrite: false, account: account, id: idActivity) if let image = results.image, let idActivity = results.id, cell.fileId == activityPreview.fileId { @@ -151,7 +151,7 @@ extension NCActivityTableViewCell: UICollectionViewDataSource { if activityPreview.isMimeTypeIcon { let source = activityPreview.source Task { - let results = await utility.convertSVGtoPNGWriteToUserData(svgUrlString: source, size: 150, rewrite: false, account: account, id: idActivity) + let results = await utility.convertSVGtoPNGWriteToUserData(fileName: source, size: 150, rewrite: false, account: account, id: idActivity) if let image = results.image, let idActivity = results.id, cell.fileId == activityPreview.fileId { diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index 3cfb399d7e..c51402a4e5 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -510,13 +510,14 @@ class NCContextMenuMain: NSObject { image.draw(in: CGRect(origin: .zero, size: size)) }.withRenderingMode(image.renderingMode) } - var iconImage = UIImage() if let iconUrl = item.icon, let url = URL(string: metadata.urlBase + iconUrl) { let (data, _) = try await URLSession.shared.data(from: url) - iconImage = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 23, height: 23)) + iconImage = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, + size: .init(width: 23, height: 23), + fileName: iconUrl) } else { iconImage = UIImage() } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index a6824723aa..23c0f1cd78 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -340,7 +340,10 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { let sortedNotifications = notifications.sorted { $0.date > $1.date } for notification in sortedNotifications { if let icon = notification.icon { - let results = await self.utility.convertSVGtoPNGWriteToUserData(svgUrlString: icon, size: 25, rewrite: false, account: session.account) + let results = await self.utility.convertSVGtoPNGWriteToUserData(fileName: icon, + size: 25, + rewrite: false, + account: session.account) if results.image != nil { self.tableView.reloadData() } diff --git a/iOSClient/Utility/NCSVGRenderer.swift b/iOSClient/Utility/NCSVGRenderer.swift index 02d818a42c..8f09541f47 100644 --- a/iOSClient/Utility/NCSVGRenderer.swift +++ b/iOSClient/Utility/NCSVGRenderer.swift @@ -13,19 +13,21 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { func renderSVGToUIImage(svgData: Data, size: CGSize, - fileName: String, + fileName: String? = nil, backgroundColor: UIColor = .clear) async throws -> UIImage { // try to get from directoryUserData - let fileName = fileName.replacingOccurrences(of: ".svg", with: ".png") - let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) - if FileManager.default.fileExists(atPath: path) { - do { - let url = URL(fileURLWithPath: path) - let data = try Data(contentsOf: url) - if let image = UIImage(data: data) { - return image - } - } catch { } + if let fileName { + let fileName = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: fileName).lastPathComponent, with: "png") + let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) + if FileManager.default.fileExists(atPath: path) { + do { + let url = URL(fileURLWithPath: path) + let data = try Data(contentsOf: url) + if let image = UIImage(data: data) { + return image + } + } catch { } + } } let webView = WKWebView(frame: CGRect(origin: .zero, size: size)) @@ -76,7 +78,11 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { config.rect = CGRect(origin: .zero, size: size) let image = try await takeSnapshotAsync(webView: webView, configuration: config) - if let data = image.pngData() { + + if let fileName, + let data = image.pngData() { + let fileName = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: fileName).lastPathComponent, with: "png") + let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) try data.write(to: URL(fileURLWithPath: path)) } diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index 7c39f3148c..bba29fb7b3 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -260,29 +260,23 @@ extension NCUtility { return avatarImage } - func convertSVGtoPNGWriteToUserData(svgUrlString: String, - fileName: String? = nil, + func convertSVGtoPNGWriteToUserData(fileName: String, size: CGFloat, rewrite: Bool, account: String, id: Int? = nil) async -> (image: UIImage?, id: Int?) { - var fileNamePNG = "" - guard let svgUrlString = svgUrlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let iconURL = URL(string: svgUrlString) else { - return (nil, id) + var serverUrl: String? + if let url = fileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { + serverUrl = URL(string: url)?.absoluteString } - if let fileName { - fileNamePNG = fileName - } else { - fileNamePNG = iconURL.deletingPathExtension().lastPathComponent + ".png" - } - let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNamePNG) + let fileName = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: fileName).lastPathComponent, with: "png") + let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) - if !FileManager.default.fileExists(atPath: path) || rewrite { - let results = await NextcloudKit.shared.downloadContentAsync(serverUrl: iconURL.absoluteString, account: account) { task in + if let serverUrl, (!FileManager.default.fileExists(atPath: path) || rewrite) { + let results = await NextcloudKit.shared.downloadContentAsync(serverUrl: serverUrl, account: account) { task in Task { let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, - path: iconURL.absoluteString, + path: serverUrl, name: "downloadContent") await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) } @@ -316,7 +310,7 @@ extension NCUtility { // is a SVG do { - let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: CGSize(width: size, height: size)) + let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: CGSize(width: size, height: size), fileName: fileName) guard let pngImageData = image.pngData() else { return(nil, id) } diff --git a/iOSClient/Utility/NCUtilityFileSystem.swift b/iOSClient/Utility/NCUtilityFileSystem.swift index 530ca86af4..95f209cd1a 100644 --- a/iOSClient/Utility/NCUtilityFileSystem.swift +++ b/iOSClient/Utility/NCUtilityFileSystem.swift @@ -354,6 +354,13 @@ final class NCUtilityFileSystem: NSObject, @unchecked Sendable { } } + func replaceExtension(of fileName: String, with newExtension: String) -> String { + URL(fileURLWithPath: fileName) + .deletingPathExtension() + .appendingPathExtension(newExtension) + .lastPathComponent + } + func getFileCreationDate(filePath: String) -> NSDate? { do { let attributes = try fileManager.attributesOfItem(atPath: filePath) From ac060e0263cf2d6a895a420a5f9c9d66cc1f5c38 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 5 Feb 2026 14:22:47 +0100 Subject: [PATCH 05/20] code Signed-off-by: Marino Faggiana --- Widget/Dashboard/DashboardData.swift | 1 - iOSClient/Activity/NCActivity.swift | 53 ++++++------------- .../Activity/NCActivityTableViewCell.swift | 10 ++-- iOSClient/Menu/NCContextMenuMain.swift | 18 ++----- iOSClient/Menu/NCContextMenuProfile.swift | 3 +- iOSClient/Notification/NCNotification.swift | 1 - iOSClient/Utility/NCSVGRenderer.swift | 9 ++-- iOSClient/Utility/NCUtility+Image.swift | 7 +-- .../Viewer/NCViewerMedia/NCViewerMedia.swift | 19 +++---- 9 files changed, 47 insertions(+), 74 deletions(-) diff --git a/Widget/Dashboard/DashboardData.swift b/Widget/Dashboard/DashboardData.swift index e688a7cc2a..89ec1669db 100644 --- a/Widget/Dashboard/DashboardData.swift +++ b/Widget/Dashboard/DashboardData.swift @@ -87,7 +87,6 @@ func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?, use } else { do { imageData = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, - size: size, fileName: fileNameToWrite) } catch { print("Unsupported image format: \(error.localizedDescription)") diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index f45e7e3bf4..61231cba95 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -273,46 +273,27 @@ extension NCActivity: UITableViewDataSource { // icon if !activity.icon.isEmpty { - activity.icon = activity.icon.replacingOccurrences(of: ".png", with: ".svg") - let fileNameIcon = (activity.icon as NSString).lastPathComponent - let fileNameLocalPath = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNameIcon) - - if FileManager.default.fileExists(atPath: fileNameLocalPath), - fileNameIcon.contains(".svg") { - Task { - do { - let url = URL(fileURLWithPath: fileNameLocalPath) - let data = try Data(contentsOf: url) - if let image = UIImage(data: data) { - cell.icon.image = image - } else { - let image = try await NCSVGRenderer().renderSVGToUIImage( - svgData: data, - size: CGSize(width: 24, height: 24), - backgroundColor: .clear - ) + Task { + let fileName = (activity.icon as NSString).lastPathComponent + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: nil, fileName: fileName) { + if cell.idActivity == activity.idActivity { + cell.icon.image = image + } + } else { + let results = await NextcloudKit.shared.downloadContentAsync(serverUrl: activity.icon, account: activity.account) { task in + Task { + let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.account, + path: activity.icon, + name: "downloadContent") + await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) + } + } + if let data = results.responseData?.data { + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: nil, fileName: fileName) { if cell.idActivity == activity.idActivity { cell.icon.image = image } } - } catch { - print("SVG render failed: \(error)") - } - } - } else { - NextcloudKit.shared.downloadContent(serverUrl: activity.icon, account: activity.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.account, - path: activity.icon, - name: "downloadContent") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } completion: { _, responseData, error in - if error == .success, let data = responseData?.data { - do { - try data.write(to: NSURL(fileURLWithPath: fileNameLocalPath) as URL, options: .atomic) - self.tableView.reloadData() - } catch { return } } } } diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index ff36d38da5..286e389ebb 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -140,10 +140,9 @@ extension NCActivityTableViewCell: UICollectionViewDataSource { if activityPreview.view == "trashbin" { let source = activityPreview.source Task { - let results = await utility.convertSVGtoPNGWriteToUserData(fileName: source, size: 100, rewrite: false, account: account, id: idActivity) + let results = await utility.convertSVGtoPNGWriteToUserData(fileName: source, rewrite: false, account: account, id: idActivity) if let image = results.image, - let idActivity = results.id, - cell.fileId == activityPreview.fileId { + cell.fileId == results.id { cell.imageView.image = image } } @@ -151,10 +150,9 @@ extension NCActivityTableViewCell: UICollectionViewDataSource { if activityPreview.isMimeTypeIcon { let source = activityPreview.source Task { - let results = await utility.convertSVGtoPNGWriteToUserData(fileName: source, size: 150, rewrite: false, account: account, id: idActivity) + let results = await utility.convertSVGtoPNGWriteToUserData(fileName: source, rewrite: false, account: account, id: idActivity) if let image = results.image, - let idActivity = results.id, - cell.fileId == activityPreview.fileId { + cell.fileId == results.id { cell.imageView.image = image } } diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index c51402a4e5..337c7b6649 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -502,24 +502,14 @@ class NCContextMenuMain: NSObject { if shouldShowMenu { let deferredElement = UIDeferredMenuElement { completion in Task { - func resizedRasterImage(_ image: UIImage, to size: CGSize) -> UIImage { - let format = UIGraphicsImageRendererFormat.default() - format.scale = image.scale - let renderer = UIGraphicsImageRenderer(size: size, format: format) - return renderer.image { _ in - image.draw(in: CGRect(origin: .zero, size: size)) - }.withRenderingMode(image.renderingMode) - } var iconImage = UIImage() - if let iconUrl = item.icon, let url = URL(string: metadata.urlBase + iconUrl) { let (data, _) = try await URLSession.shared.data(from: url) - iconImage = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, - size: .init(width: 23, height: 23), - fileName: iconUrl) - } else { - iconImage = UIImage() + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, + fileName: iconUrl) { + iconImage = image + } } let action = await UIAction( diff --git a/iOSClient/Menu/NCContextMenuProfile.swift b/iOSClient/Menu/NCContextMenuProfile.swift index 3cb8828c1f..79f77c65d7 100644 --- a/iOSClient/Menu/NCContextMenuProfile.swift +++ b/iOSClient/Menu/NCContextMenuProfile.swift @@ -103,12 +103,13 @@ class NCContextMenuProfile: NSObject { Task { @MainActor in do { let data = try Data(contentsOf: url) - let image = try await NCSVGRenderer().renderSVGToUIImage( svgData: data, size: CGSize(width: 24, height: 24)) + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data) { let tinted = image.withTintColor( NCBrandColor.shared.iconImageColor, renderingMode: .alwaysOriginal ) uiAction.image = tinted + } } catch { nkLog(error: "SVG render failed: \(error)") } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index 23c0f1cd78..817a376d11 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -341,7 +341,6 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { for notification in sortedNotifications { if let icon = notification.icon { let results = await self.utility.convertSVGtoPNGWriteToUserData(fileName: icon, - size: 25, rewrite: false, account: session.account) if results.image != nil { diff --git a/iOSClient/Utility/NCSVGRenderer.swift b/iOSClient/Utility/NCSVGRenderer.swift index 8f09541f47..58b43b1423 100644 --- a/iOSClient/Utility/NCSVGRenderer.swift +++ b/iOSClient/Utility/NCSVGRenderer.swift @@ -11,10 +11,10 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { private var webView: WKWebView? private let utilityFileSystem = NCUtilityFileSystem() - func renderSVGToUIImage(svgData: Data, - size: CGSize, + func renderSVGToUIImage(svgData: Data?, + size: CGSize = CGSize(width: 128, height: 128), fileName: String? = nil, - backgroundColor: UIColor = .clear) async throws -> UIImage { + backgroundColor: UIColor = .clear) async throws -> UIImage? { // try to get from directoryUserData if let fileName { let fileName = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: fileName).lastPathComponent, with: "png") @@ -29,6 +29,9 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { } catch { } } } + guard let svgData else { + return nil + } let webView = WKWebView(frame: CGRect(origin: .zero, size: size)) self.webView = webView diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index bba29fb7b3..d5ed8ec158 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -261,7 +261,7 @@ extension NCUtility { } func convertSVGtoPNGWriteToUserData(fileName: String, - size: CGFloat, + size: CGFloat = 128, rewrite: Bool, account: String, id: Int? = nil) async -> (image: UIImage?, id: Int?) { @@ -311,8 +311,9 @@ extension NCUtility { // is a SVG do { let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: CGSize(width: size, height: size), fileName: fileName) - guard let pngImageData = image.pngData() else { - return(nil, id) + guard let image, + let pngImageData = image.pngData() else { + return(nil, id) } try pngImageData.write(to: URL(fileURLWithPath: path)) return(image, id) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index d876849486..15524233f0 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -305,16 +305,17 @@ class NCViewerMedia: UIViewController { do { let url = URL(fileURLWithPath: fileNamePath) let data = try Data(contentsOf: url) - let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 1024, height: 1024)) - if !NCUtility().existsImage(ocId: metadata.ocId, - etag: metadata.etag, - ext: global.previewExt1024, - userId: metadata.userId, - urlBase: metadata.urlBase), let data = image.jpegData(compressionQuality: 1.0) { - utility.createImageFileFrom(data: data, metadata: metadata) + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 1024, height: 1024)) { + if !NCUtility().existsImage(ocId: metadata.ocId, + etag: metadata.etag, + ext: global.previewExt1024, + userId: metadata.userId, + urlBase: metadata.urlBase), let data = image.jpegData(compressionQuality: 1.0) { + utility.createImageFileFrom(data: data, metadata: metadata) + } + self.image = image + self.imageVideoContainer.image = self.image } - self.image = image - self.imageVideoContainer.image = self.image return } catch { print("Unsupported image format: \(error.localizedDescription)") From 5e0a0ebd1106059a755f72d708408e4f54ce6a28 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 5 Feb 2026 14:30:37 +0100 Subject: [PATCH 06/20] code Signed-off-by: Marino Faggiana --- iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index 15524233f0..872487b34e 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -249,6 +249,7 @@ class NCViewerMedia: UIViewController { // MARK: - Image + @MainActor func loadImage() async { guard let metadata = self.database.getMetadataFromOcId(metadata.ocId) else { return } self.metadata = metadata @@ -305,7 +306,7 @@ class NCViewerMedia: UIViewController { do { let url = URL(fileURLWithPath: fileNamePath) let data = try Data(contentsOf: url) - if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 1024, height: 1024)) { + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 1024, height: 1024), backgroundColor: UIColor(white: 0.95, alpha: 1)) { if !NCUtility().existsImage(ocId: metadata.ocId, etag: metadata.etag, ext: global.previewExt1024, From 04468f8cb6c5b63543242c4a4c40db931a0fba36 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 5 Feb 2026 14:41:40 +0100 Subject: [PATCH 07/20] code Signed-off-by: Marino Faggiana --- iOSClient/Utility/NCUtility+Image.swift | 2 +- iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index d5ed8ec158..e125dff8ab 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -127,7 +127,7 @@ extension NCUtility { } func createImageFileFrom(data: Data, metadata: tableMetadata) { - createImageFileFrom( data: data, ocId: metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) + createImageFileFrom(data: data, ocId: metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) } func createImageFileFrom(data: Data, ocId: String, etag: String, userId: String, urlBase: String) { diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index 872487b34e..91fa9ecf41 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -306,13 +306,14 @@ class NCViewerMedia: UIViewController { do { let url = URL(fileURLWithPath: fileNamePath) let data = try Data(contentsOf: url) - if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 1024, height: 1024), backgroundColor: UIColor(white: 0.95, alpha: 1)) { + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 1024, height: 1024), fileName: metadata.fileName, backgroundColor: UIColor(white: 0.95, alpha: 1)), + let imageData = image.pngData() { if !NCUtility().existsImage(ocId: metadata.ocId, etag: metadata.etag, ext: global.previewExt1024, userId: metadata.userId, - urlBase: metadata.urlBase), let data = image.jpegData(compressionQuality: 1.0) { - utility.createImageFileFrom(data: data, metadata: metadata) + urlBase: metadata.urlBase) { + utility.createImageFileFrom(data: imageData, metadata: metadata) } self.image = image self.imageVideoContainer.image = self.image From d10455010795c3bdaef483ca8544f43e359e5b9a Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 10 Feb 2026 13:44:24 +0100 Subject: [PATCH 08/20] project updated Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 57 ++++------------------------- 1 file changed, 8 insertions(+), 49 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 638e4d6de0..b18132a8f4 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -100,8 +100,6 @@ F33918C42C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33918C32C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift */; }; F33918C72C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33918C32C7CD8F2002D9AA1 /* FileNameValidator+Extensions.swift */; }; F3391B082B4C52C5001C0C4B /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = F3391B072B4C52C5001C0C4B /* FirebaseDatabase */; }; - F3391B0C2B4C52D5001C0C4B /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = F3391B0B2B4C52D5001C0C4B /* SVGKit */; }; - F3391B102B4C52E6001C0C4B /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = F3391B0F2B4C52E6001C0C4B /* SVGKit */; }; F3391B162B4C52F6001C0C4B /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = F3391B152B4C52F6001C0C4B /* FirebaseDatabase */; }; F33D303E2D8B129600531D64 /* AutoUploadUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F33D303D2D8B129600531D64 /* AutoUploadUITests.swift */; }; F33EE6E12BF4BDA500CA1A51 /* NIOSSL in Frameworks */ = {isa = PBXBuildFile; productRef = F33EE6E02BF4BDA500CA1A51 /* NIOSSL */; }; @@ -233,7 +231,6 @@ F711A4E52AF9310500095DD8 /* NCUtility+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF93474B27E34120002537EE /* NCUtility+Image.swift */; }; F711A4E92AF9327600095DD8 /* UIImage+animatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = F713FEFF2472764100214AF6 /* UIImage+animatedGIF.m */; }; F711A4EB2AF9327D00095DD8 /* UIImage+animatedGIF.m in Sources */ = {isa = PBXBuildFile; fileRef = F713FEFF2472764100214AF6 /* UIImage+animatedGIF.m */; }; - F711A4EF2AF932B900095DD8 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F711A4EE2AF932B900095DD8 /* SVGKitSwift */; }; F711D63128F44801003F43C8 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C9739428F17131002C43E2 /* IntentHandler.swift */; }; F7132C722D085AD200B42D6A /* NCTermOfServiceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7132C6B2D085AD200B42D6A /* NCTermOfServiceModel.swift */; }; F7132C732D085AD200B42D6A /* NCTermOfServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7132C6C2D085AD200B42D6A /* NCTermOfServiceView.swift */; }; @@ -571,6 +568,9 @@ F78026102E9CFA3700B63436 /* NCTransfersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F780260F2E9CF9DE00B63436 /* NCTransfersView.swift */; }; F78026122E9CFA6300B63436 /* NCTransfersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78026112E9CFA6000B63436 /* NCTransfersModel.swift */; }; F7802B322BD5584F00D74270 /* NCMedia+DragDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7802B312BD5584F00D74270 /* NCMedia+DragDrop.swift */; }; + F7814E962F3B5F170074DA3A /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7814E952F3B5F170074DA3A /* NCSVGRenderer.swift */; }; + F7814E972F3B5F170074DA3A /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7814E952F3B5F170074DA3A /* NCSVGRenderer.swift */; }; + F7814E982F3B5F580074DA3A /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7814E952F3B5F170074DA3A /* NCSVGRenderer.swift */; }; F7816EF22C2C3E1F00A52517 /* NCPushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7816EF12C2C3E1F00A52517 /* NCPushNotification.swift */; }; F7817CF829801A3500FFBC65 /* Data+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7817CF729801A3500FFBC65 /* Data+Extension.swift */; }; F7817CFB29801A3500FFBC65 /* Data+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7817CF729801A3500FFBC65 /* Data+Extension.swift */; }; @@ -601,8 +601,6 @@ F7864AD22A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7865FF12F39D32F00D09AE4 /* NCCollectionViewCommon+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7865FF02F39D32500D09AE4 /* NCCollectionViewCommon+Search.swift */; }; F787704F22E7019900F287A9 /* NCShareLinkCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F787704E22E7019900F287A9 /* NCShareLinkCell.xib */; }; - F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC08298BCB4A0001BB00 /* SVGKitSwift */; }; - F787AC0B298BCB540001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC0A298BCB540001BB00 /* SVGKitSwift */; }; F788ECC7263AAAFA00ADC67F /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = F788ECC6263AAAFA00ADC67F /* MarkdownKit */; }; F78A10BF29322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78A10BE29322E8A008499B8 /* NCManageDatabase+Directory.swift */; }; F78A10C029322E8A008499B8 /* NCManageDatabase+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78A10BE29322E8A008499B8 /* NCManageDatabase+Directory.swift */; }; @@ -1521,6 +1519,7 @@ F780260F2E9CF9DE00B63436 /* NCTransfersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCTransfersView.swift; sourceTree = ""; }; F78026112E9CFA6000B63436 /* NCTransfersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCTransfersModel.swift; sourceTree = ""; }; F7802B312BD5584F00D74270 /* NCMedia+DragDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMedia+DragDrop.swift"; sourceTree = ""; }; + F7814E952F3B5F170074DA3A /* NCSVGRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCSVGRenderer.swift; sourceTree = ""; }; F7816EF12C2C3E1F00A52517 /* NCPushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCPushNotification.swift; sourceTree = ""; }; F7817CF729801A3500FFBC65 /* Data+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extension.swift"; sourceTree = ""; }; F785129A2D79899E0087DDD0 /* NCNetworking+TermsOfService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+TermsOfService.swift"; sourceTree = ""; }; @@ -1847,7 +1846,6 @@ F37208B62BAB63EF006B5430 /* EasyTipView in Frameworks */, F37208BA2BAB63EF006B5430 /* TLPhotoPicker in Frameworks */, F37208AA2BAB63EE006B5430 /* MarkdownKit in Frameworks */, - F3391B102B4C52E6001C0C4B /* SVGKit in Frameworks */, F37208AE2BAB63EE006B5430 /* MarqueeLabel in Frameworks */, F37208C02BAB63F0006B5430 /* Mantis in Frameworks */, F37208C62BAB63F0006B5430 /* LRUCache in Frameworks */, @@ -1867,7 +1865,6 @@ files = ( F3F0419D2B9F7E6E00D5155F /* RealmSwift in Frameworks */, F3391B082B4C52C5001C0C4B /* FirebaseDatabase in Frameworks */, - F3391B0C2B4C52D5001C0C4B /* SVGKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1885,7 +1882,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F711A4EF2AF932B900095DD8 /* SVGKitSwift in Frameworks */, F710FC80277B7D2700AA9FBF /* RealmSwift in Frameworks */, F74C863D2AEFBFD9009A1D4A /* LRUCache in Frameworks */, F70557B92ED44E4700135623 /* LucidBanner in Frameworks */, @@ -1906,7 +1902,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F787AC0B298BCB540001BB00 /* SVGKitSwift in Frameworks */, F783034428B5142B00B84583 /* NextcloudKit in Frameworks */, F33EE6E32BF4C00700CA1A51 /* NIOSSL in Frameworks */, F7160A7D2BE931DE0034DCB3 /* RealmSwift in Frameworks */, @@ -1941,7 +1936,6 @@ F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */, F33EE6E12BF4BDA500CA1A51 /* NIOSSL in Frameworks */, F734B06628E75C0100E180D5 /* TLPhotoPicker in Frameworks */, - F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */, F760DE032AE66EA80027D78A /* KeychainAccess in Frameworks */, F3374AF62D78B01B002A38F9 /* HashTreeCollections in Frameworks */, F33EE6F02BF4C0FF00CA1A51 /* NIO in Frameworks */, @@ -2939,6 +2933,7 @@ F7A560412AE1593700BE8FD6 /* NCOperationSaveLivePhoto.swift */, F702F30725EE5D47008F8E80 /* NCPopupViewController.swift */, F707C26421A2DC5200F6181E /* NCStoreReview.swift */, + F7814E952F3B5F170074DA3A /* NCSVGRenderer.swift */, F70BFC7320E0FA7C00C67599 /* NCUtility.swift */, F711A4DB2AF92CAD00095DD8 /* NCUtility+Date.swift */, F359D8662A7D03420023F405 /* NCUtility+Exif.swift */, @@ -3426,7 +3421,6 @@ ); name = NextcloudUITests; packageProductDependencies = ( - F3391B0F2B4C52E6001C0C4B /* SVGKit */, F3391B152B4C52F6001C0C4B /* FirebaseDatabase */, F3F0419E2B9F7E7900D5155F /* RealmSwift */, F37208A32BAB63EE006B5430 /* QRCodeReader */, @@ -3469,7 +3463,6 @@ name = NextcloudIntegrationTests; packageProductDependencies = ( F3391B072B4C52C5001C0C4B /* FirebaseDatabase */, - F3391B0B2B4C52D5001C0C4B /* SVGKit */, F3F0419C2B9F7E6E00D5155F /* RealmSwift */, ); productName = NextcloudIntegrationTests; @@ -3524,7 +3517,6 @@ F7A560472AE15D5000BE8FD6 /* Queuer */, F760DE082AE66ED00027D78A /* KeychainAccess */, F74C863C2AEFBFD9009A1D4A /* LRUCache */, - F711A4EE2AF932B900095DD8 /* SVGKitSwift */, F33EE6E62BF4C02600CA1A51 /* NIOSSL */, F7B8F6172EAB7516006A70D6 /* JDStatusBarNotification */, F70557B82ED44E4700135623 /* LucidBanner */, @@ -3550,7 +3542,6 @@ packageProductDependencies = ( F783030C28B4C59A00B84583 /* SwiftEntryKit */, F783034328B5142B00B84583 /* NextcloudKit */, - F787AC0A298BCB540001BB00 /* SVGKitSwift */, F7A560452AE15D3D00BE8FD6 /* Queuer */, F760DE042AE66EBE0027D78A /* KeychainAccess */, F7160A7C2BE931DE0034DCB3 /* RealmSwift */, @@ -3622,7 +3613,6 @@ F77333872927A72100466E35 /* OpenSSL */, F77BC3EA293E5268005F2B08 /* Swifter */, F7D56B192972405500FA46C4 /* Mantis */, - F787AC08298BCB4A0001BB00 /* SVGKitSwift */, F7A1050D29E587AF00FFD92B /* TagListView */, F7F623B42A5EF4D30022D3D4 /* Gzip */, F76B649D2ADFFDEC00014640 /* LRUCache */, @@ -3794,7 +3784,6 @@ ); mainGroup = F7F67B9F1A24D27800EE80DA; packageReferences = ( - F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */, F7ED547A25EEA65400956C55 /* XCRemoteSwiftPackageReference "QRCodeReader" */, F72DA9B225F53E4E00B87DB1 /* XCRemoteSwiftPackageReference "SwiftRichString" */, F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */, @@ -4255,6 +4244,7 @@ F799DF832C4B7DCC003410B5 /* NCSectionFooter.swift in Sources */, AF22B218277D196700DAB0CC /* NCShareExtension+Files.swift in Sources */, F799DF862C4B7E56003410B5 /* NCSectionHeader.swift in Sources */, + F7814E962F3B5F170074DA3A /* NCSVGRenderer.swift in Sources */, F702F2D025EE5B5C008F8E80 /* NCGlobal.swift in Sources */, F72437802C10B92400C7C68D /* NCSharePermissions.swift in Sources */, F7EDE4DB262D7BA200414FE6 /* NCCellProtocol.swift in Sources */, @@ -4345,6 +4335,7 @@ F724377C2C10B92200C7C68D /* NCSharePermissions.swift in Sources */, F77ED59328C9CEA000E24ED0 /* ToolbarWidgetProvider.swift in Sources */, F72A17D828B221E300F3F159 /* DashboardWidgetView.swift in Sources */, + F7814E982F3B5F580074DA3A /* NCSVGRenderer.swift in Sources */, F77ED59528C9CEA400E24ED0 /* ToolbarWidgetView.swift in Sources */, F78302FB28B4C3EE00B84583 /* NCManageDatabase+Video.swift in Sources */, F7C687EA2D22BDE5004757BC /* NCManageDatabase+RecommendedFiles.swift in Sources */, @@ -4678,6 +4669,7 @@ F7AC1CB028AB94490032D99F /* Array+Extension.swift in Sources */, F7AE00F5230D5F9E007ACF8A /* NCLoginProvider.swift in Sources */, F707C26521A2DC5200F6181E /* NCStoreReview.swift in Sources */, + F7814E972F3B5F170074DA3A /* NCSVGRenderer.swift in Sources */, F7CF06802E0FF3990063AD04 /* NCAppStateManager.swift in Sources */, F7BAADCB1ED5A87C00B7EAD4 /* NCManageDatabase.swift in Sources */, F768822A2C0DD1E7001CF441 /* NCSettingsModel.swift in Sources */, @@ -6054,14 +6046,6 @@ kind = branch; }; }; - F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SVGKit/SVGKit.git"; - requirement = { - kind = upToNextMinorVersion; - minimumVersion = 3.0.0; - }; - }; F75EAED626D2552E00F4320E /* XCRemoteSwiftPackageReference "MarqueeLabel" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/cbpowell/MarqueeLabel"; @@ -6223,16 +6207,6 @@ package = F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseDatabase; }; - F3391B0B2B4C52D5001C0C4B /* SVGKit */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKit; - }; - F3391B0F2B4C52E6001C0C4B /* SVGKit */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKit; - }; F3391B152B4C52F6001C0C4B /* FirebaseDatabase */ = { isa = XCSwiftPackageProductDependency; package = F70B86732642CE3B00ED5349 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; @@ -6413,11 +6387,6 @@ package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; - F711A4EE2AF932B900095DD8 /* SVGKitSwift */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKitSwift; - }; F7160A7C2BE931DE0034DCB3 /* RealmSwift */ = { isa = XCSwiftPackageProductDependency; package = F710FC78277B7CFF00AA9FBF /* XCRemoteSwiftPackageReference "realm-swift" */; @@ -6573,16 +6542,6 @@ package = F783034028B511D200B84583 /* XCRemoteSwiftPackageReference "NextcloudKit" */; productName = NextcloudKit; }; - F787AC08298BCB4A0001BB00 /* SVGKitSwift */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKitSwift; - }; - F787AC0A298BCB540001BB00 /* SVGKitSwift */ = { - isa = XCSwiftPackageProductDependency; - package = F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */; - productName = SVGKitSwift; - }; F788ECC6263AAAFA00ADC67F /* MarkdownKit */ = { isa = XCSwiftPackageProductDependency; package = F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */; From 90c624778104277d3c5b22691b82ffee5fd3d3ed Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 10 Feb 2026 13:46:32 +0100 Subject: [PATCH 09/20] clean Signed-off-by: Marino Faggiana --- Widget/Dashboard/DashboardData.swift | 6 ++++-- iOSClient/Menu/NCContextMenuMain.swift | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Widget/Dashboard/DashboardData.swift b/Widget/Dashboard/DashboardData.swift index 89ec1669db..467ecf9eab 100644 --- a/Widget/Dashboard/DashboardData.swift +++ b/Widget/Dashboard/DashboardData.swift @@ -86,8 +86,10 @@ func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?, use imageData = image } else { do { - imageData = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, - fileName: fileNameToWrite) + imageData = try await NCSVGRenderer().renderSVGToUIImage( + svgData: data, + fileName: fileNameToWrite + ) } catch { print("Unsupported image format: \(error.localizedDescription)") } diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index 337c7b6649..3bb057632d 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -506,8 +506,10 @@ class NCContextMenuMain: NSObject { if let iconUrl = item.icon, let url = URL(string: metadata.urlBase + iconUrl) { let (data, _) = try await URLSession.shared.data(from: url) - if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, - fileName: iconUrl) { + if let image = try await NCSVGRenderer().renderSVGToUIImage( + svgData: data, + fileName: iconUrl + ) { iconImage = image } } @@ -517,13 +519,14 @@ class NCContextMenuMain: NSObject { image: iconImage ) { _ in Task { - let results = await NextcloudKit.shared.sendRequestAsync(account: metadata.account, - fileId: metadata.fileId, - filePath: self.utilityFileSystem.getRelativeFilePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId), - url: item.url, - method: item.method, - params: item.params) - + let results = await NextcloudKit.shared.sendRequestAsync( + account: metadata.account, + fileId: metadata.fileId, + filePath: self.utilityFileSystem.getRelativeFilePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId), + url: item.url, + method: item.method, + params: item.params + ) if results.error != .success { await showErrorBanner(sceneIdentifier: self.sceneIdentifier, text: results.error.errorDescription, errorCode: results.error.errorCode) } else { From 67e0ecdbc90304e2570aa344b3e9672e0213e396 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 10 Feb 2026 14:37:45 +0100 Subject: [PATCH 10/20] code Signed-off-by: Marino Faggiana --- iOSClient/Activity/NCActivity.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 61231cba95..93bd63ea60 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -289,7 +289,7 @@ extension NCActivity: UITableViewDataSource { } } if let data = results.responseData?.data { - if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: nil, fileName: fileName) { + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, fileName: fileName) { if cell.idActivity == activity.idActivity { cell.icon.image = image } From 229b2aa0a0e41e134d8fbbc50e24bd93bf435a40 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 09:31:20 +0100 Subject: [PATCH 11/20] fix Signed-off-by: Marino Faggiana --- iOSClient/Activity/NCActivity.swift | 28 +++++-------------- .../Activity/NCActivityTableViewCell.swift | 4 +-- iOSClient/Menu/NCContextMenuMain.swift | 11 +++----- iOSClient/Notification/NCNotification.swift | 5 +--- iOSClient/Utility/NCSVGRenderer.swift | 22 --------------- iOSClient/Utility/NCUtility+Image.swift | 27 +++++++++--------- 6 files changed, 28 insertions(+), 69 deletions(-) diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index 93bd63ea60..9959cf54c6 100644 --- a/iOSClient/Activity/NCActivity.swift +++ b/iOSClient/Activity/NCActivity.swift @@ -274,27 +274,13 @@ extension NCActivity: UITableViewDataSource { // icon if !activity.icon.isEmpty { Task { - let fileName = (activity.icon as NSString).lastPathComponent - if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: nil, fileName: fileName) { - if cell.idActivity == activity.idActivity { - cell.icon.image = image - } - } else { - let results = await NextcloudKit.shared.downloadContentAsync(serverUrl: activity.icon, account: activity.account) { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: self.account, - path: activity.icon, - name: "downloadContent") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } - if let data = results.responseData?.data { - if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, fileName: fileName) { - if cell.idActivity == activity.idActivity { - cell.icon.image = image - } - } - } + let results = await NCUtility().convertSVGtoPNGWriteToUserData(serverUrl: activity.icon, + rewrite: false, + account: activity.account, + id: activity.idActivity) + if let image = results.image, + cell.idActivity == results.id { + cell.icon.image = image } } } diff --git a/iOSClient/Activity/NCActivityTableViewCell.swift b/iOSClient/Activity/NCActivityTableViewCell.swift index 286e389ebb..67df203544 100644 --- a/iOSClient/Activity/NCActivityTableViewCell.swift +++ b/iOSClient/Activity/NCActivityTableViewCell.swift @@ -140,7 +140,7 @@ extension NCActivityTableViewCell: UICollectionViewDataSource { if activityPreview.view == "trashbin" { let source = activityPreview.source Task { - let results = await utility.convertSVGtoPNGWriteToUserData(fileName: source, rewrite: false, account: account, id: idActivity) + let results = await utility.convertSVGtoPNGWriteToUserData(serverUrl: source, rewrite: false, account: account, id: idActivity) if let image = results.image, cell.fileId == results.id { cell.imageView.image = image @@ -150,7 +150,7 @@ extension NCActivityTableViewCell: UICollectionViewDataSource { if activityPreview.isMimeTypeIcon { let source = activityPreview.source Task { - let results = await utility.convertSVGtoPNGWriteToUserData(fileName: source, rewrite: false, account: account, id: idActivity) + let results = await utility.convertSVGtoPNGWriteToUserData(serverUrl: source, rewrite: false, account: account, id: idActivity) if let image = results.image, cell.fileId == results.id { cell.imageView.image = image diff --git a/iOSClient/Menu/NCContextMenuMain.swift b/iOSClient/Menu/NCContextMenuMain.swift index 3bb057632d..c57af4a957 100644 --- a/iOSClient/Menu/NCContextMenuMain.swift +++ b/iOSClient/Menu/NCContextMenuMain.swift @@ -503,13 +503,10 @@ class NCContextMenuMain: NSObject { let deferredElement = UIDeferredMenuElement { completion in Task { var iconImage = UIImage() - if let iconUrl = item.icon, - let url = URL(string: metadata.urlBase + iconUrl) { - let (data, _) = try await URLSession.shared.data(from: url) - if let image = try await NCSVGRenderer().renderSVGToUIImage( - svgData: data, - fileName: iconUrl - ) { + if let iconUrl = item.icon { + if let image = await NCUtility().convertSVGtoPNGWriteToUserData(serverUrl: metadata.urlBase + iconUrl, + rewrite: false, + account: metadata.account).image { iconImage = image } } diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index 817a376d11..84fc727b1e 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -340,10 +340,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { let sortedNotifications = notifications.sorted { $0.date > $1.date } for notification in sortedNotifications { if let icon = notification.icon { - let results = await self.utility.convertSVGtoPNGWriteToUserData(fileName: icon, - rewrite: false, - account: session.account) - if results.image != nil { + if await self.utility.convertSVGtoPNGWriteToUserData(serverUrl: icon, rewrite: false, account: session.account).image != nil { self.tableView.reloadData() } } diff --git a/iOSClient/Utility/NCSVGRenderer.swift b/iOSClient/Utility/NCSVGRenderer.swift index 58b43b1423..d453b8933b 100644 --- a/iOSClient/Utility/NCSVGRenderer.swift +++ b/iOSClient/Utility/NCSVGRenderer.swift @@ -13,22 +13,7 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { func renderSVGToUIImage(svgData: Data?, size: CGSize = CGSize(width: 128, height: 128), - fileName: String? = nil, backgroundColor: UIColor = .clear) async throws -> UIImage? { - // try to get from directoryUserData - if let fileName { - let fileName = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: fileName).lastPathComponent, with: "png") - let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) - if FileManager.default.fileExists(atPath: path) { - do { - let url = URL(fileURLWithPath: path) - let data = try Data(contentsOf: url) - if let image = UIImage(data: data) { - return image - } - } catch { } - } - } guard let svgData else { return nil } @@ -82,13 +67,6 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { let image = try await takeSnapshotAsync(webView: webView, configuration: config) - if let fileName, - let data = image.pngData() { - let fileName = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: fileName).lastPathComponent, with: "png") - let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) - try data.write(to: URL(fileURLWithPath: path)) - } - return image } diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index e125dff8ab..5306a90ca7 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -131,7 +131,9 @@ extension NCUtility { } func createImageFileFrom(data: Data, ocId: String, etag: String, userId: String, urlBase: String) { - guard let image = UIImage(data: data) else { return } + guard let image = UIImage(data: data) else { + return + } let fileNamePath1024 = self.utilityFileSystem.getDirectoryProviderStorageImageOcId(ocId, etag: etag, ext: global.previewExt1024, @@ -260,19 +262,19 @@ extension NCUtility { return avatarImage } - func convertSVGtoPNGWriteToUserData(fileName: String, + func convertSVGtoPNGWriteToUserData(serverUrl: String, size: CGFloat = 128, rewrite: Bool, account: String, id: Int? = nil) async -> (image: UIImage?, id: Int?) { - var serverUrl: String? - if let url = fileName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { - serverUrl = URL(string: url)?.absoluteString + var serverUrl = serverUrl + if let url = serverUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { + serverUrl = URL(string: url)?.absoluteString ?? serverUrl } - let fileName = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: fileName).lastPathComponent, with: "png") - let path = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName) + let fileNamePNG = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: serverUrl).lastPathComponent, with: "png") + let pathPNG = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNamePNG) - if let serverUrl, (!FileManager.default.fileExists(atPath: path) || rewrite) { + if !FileManager.default.fileExists(atPath: pathPNG) || rewrite { let results = await NextcloudKit.shared.downloadContentAsync(serverUrl: serverUrl, account: account) { task in Task { let identifier = await NCNetworking.shared.networkingTasks.createIdentifier(account: account, @@ -301,7 +303,7 @@ extension NCUtility { } do { - try pngImageData.write(to: URL(fileURLWithPath: path)) + try pngImageData.write(to: URL(fileURLWithPath: pathPNG)) return(newImage, id) } catch { return(nil, id) @@ -310,20 +312,19 @@ extension NCUtility { // is a SVG do { - let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: CGSize(width: size, height: size), fileName: fileName) + let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: CGSize(width: size, height: size)) guard let image, let pngImageData = image.pngData() else { return(nil, id) } - try pngImageData.write(to: URL(fileURLWithPath: path)) + try pngImageData.write(to: URL(fileURLWithPath: pathPNG)) return(image, id) } catch { return(nil, id) } } else { do { - let url = URL(fileURLWithPath: path) - let data = try Data(contentsOf: url) + let data = try Data(contentsOf: URL(fileURLWithPath: pathPNG)) return(UIImage(data: data), id) } catch { return(nil, id) From 235856296a9f60a1921af360259cac85c0d2a375 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 10:29:19 +0100 Subject: [PATCH 12/20] code Signed-off-by: Marino Faggiana --- iOSClient/Utility/NCUtility+Image.swift | 2 +- iOSClient/Utility/NCUtilityFileSystem.swift | 9 +++++- .../Viewer/NCViewerMedia/NCViewerMedia.swift | 30 +++++++++---------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index 5306a90ca7..1604b3b4f2 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -271,7 +271,7 @@ extension NCUtility { if let url = serverUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { serverUrl = URL(string: url)?.absoluteString ?? serverUrl } - let fileNamePNG = utilityFileSystem.replaceExtension(of: URL(fileURLWithPath: serverUrl).lastPathComponent, with: "png") + let fileNamePNG = utilityFileSystem.replaceExtension(fileName: URL(fileURLWithPath: serverUrl).lastPathComponent, with: "png") let pathPNG = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNamePNG) if !FileManager.default.fileExists(atPath: pathPNG) || rewrite { diff --git a/iOSClient/Utility/NCUtilityFileSystem.swift b/iOSClient/Utility/NCUtilityFileSystem.swift index 67edccbe79..426089cc22 100644 --- a/iOSClient/Utility/NCUtilityFileSystem.swift +++ b/iOSClient/Utility/NCUtilityFileSystem.swift @@ -354,13 +354,20 @@ final class NCUtilityFileSystem: NSObject, @unchecked Sendable { } } - func replaceExtension(of fileName: String, with newExtension: String) -> String { + func replaceExtension(fileName: String, with newExtension: String) -> String { URL(fileURLWithPath: fileName) .deletingPathExtension() .appendingPathExtension(newExtension) .lastPathComponent } + func replaceExtension(fileNamePath: String, with newExtension: String) -> String { + let url = URL(fileURLWithPath: fileNamePath) + .deletingPathExtension() + .appendingPathExtension(newExtension) + return url.path + } + func getFileCreationDate(filePath: String) -> NSDate? { do { let attributes = try fileManager.attributesOfItem(atPath: filePath) diff --git a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift index 91fa9ecf41..b06d71c602 100644 --- a/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift +++ b/iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift @@ -273,9 +273,7 @@ class NCViewerMedia: UIViewController { } if metadata.isImage, fileNameExtension == "GIF" || fileNameExtension == "SVG", !utilityFileSystem.fileProviderStorageExists(metadata) { - Task { - await downloadImage() - } + await downloadImage() } if metadata.isVideo && !metadata.hasPreview { @@ -304,19 +302,20 @@ class NCViewerMedia: UIViewController { return } else if fileNameExtension == "SVG" { do { - let url = URL(fileURLWithPath: fileNamePath) - let data = try Data(contentsOf: url) - if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: .init(width: 1024, height: 1024), fileName: metadata.fileName, backgroundColor: UIColor(white: 0.95, alpha: 1)), - let imageData = image.pngData() { - if !NCUtility().existsImage(ocId: metadata.ocId, - etag: metadata.etag, - ext: global.previewExt1024, - userId: metadata.userId, - urlBase: metadata.urlBase) { - utility.createImageFileFrom(data: imageData, metadata: metadata) - } - self.image = image + let fileNamePathPNG = utilityFileSystem.replaceExtension(fileNamePath: fileNamePath, with: "png") + if FileManager.default.fileExists(atPath: fileNamePathPNG) { + let data = try Data(contentsOf: URL(fileURLWithPath: fileNamePathPNG)) + self.image = UIImage(data: data) self.imageVideoContainer.image = self.image + } else { + let svgData = try Data(contentsOf: URL(fileURLWithPath: fileNamePath)) + if let image = try await NCSVGRenderer().renderSVGToUIImage(svgData: svgData, size: CGSize(width: 1024, height: 1024)), + let data = image.pngData() { + self.image = image + self.imageVideoContainer.image = self.image + try data.write(to: URL(fileURLWithPath: fileNamePathPNG)) + utility.createImageFileFrom(data: data, metadata: metadata) + } } return } catch { @@ -371,7 +370,6 @@ class NCViewerMedia: UIViewController { self.allowOpeningDetails = false } taskHandler: { _ in } self.allowOpeningDetails = true - } } From 20fc4186136676168c91edc8de7d20f67b76f688 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 10:54:35 +0100 Subject: [PATCH 13/20] code Signed-off-by: Marino Faggiana --- iOSClient/Utility/NCSVGRenderer.swift | 78 +++++++++++++++++---------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/iOSClient/Utility/NCSVGRenderer.swift b/iOSClient/Utility/NCSVGRenderer.swift index d453b8933b..35b57b569d 100644 --- a/iOSClient/Utility/NCSVGRenderer.swift +++ b/iOSClient/Utility/NCSVGRenderer.swift @@ -18,7 +18,11 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { return nil } - let webView = WKWebView(frame: CGRect(origin: .zero, size: size)) + let targetSize = size + let logicalSize = CGSize(width: max(1, targetSize.width / max(UIScreen.main.scale, 1)), + height: max(1, targetSize.height / max(UIScreen.main.scale, 1))) + + let webView = WKWebView(frame: CGRect(origin: .zero, size: logicalSize)) self.webView = webView webView.navigationDelegate = self @@ -32,58 +36,49 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { let html = """ - + - + """ try await loadHTMLAsync(webView: webView, html: html) - try await waitForImageReady(webView: webView) + try await waitForSVGReady(webView: webView) let config = WKSnapshotConfiguration() - config.rect = CGRect(origin: .zero, size: size) + config.rect = CGRect(origin: .zero, size: logicalSize) + config.afterScreenUpdates = true let image = try await takeSnapshotAsync(webView: webView, configuration: config) - - return image - } - - func xx(path: String) async throws -> UIImage? { - guard FileManager.default.fileExists(atPath: path) else { - return nil - } - - do { - let url = URL(fileURLWithPath: path) - let data = try Data(contentsOf: url) - let image = UIImage(data: data) - return image - } catch { - print("SVG render failed: \(error)") - return nil + // Upscale to requested target size using Core Graphics + let rendererFormat = UIGraphicsImageRendererFormat.default() + rendererFormat.scale = 1.0 + let renderer = UIGraphicsImageRenderer(size: targetSize, format: rendererFormat) + let scaled = renderer.image { _ in + image.draw(in: CGRect(origin: .zero, size: targetSize)) } + return scaled } private func loadHTMLAsync(webView: WKWebView, html: String) async throws { @@ -110,6 +105,30 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { } } + private func waitForSVGReady(webView: WKWebView) async throws { + let js = """ + (function() { + const svg = document.querySelector('#svgRoot') || document.querySelector('svg'); + if (!svg) return false; + try { + if (svg.getBBox) { + const bb = svg.getBBox(); + return bb && bb.width > 0 && bb.height > 0; + } + } catch (e) { + // Some SVGs may throw on getBBox; consider ready if the element exists + return true; + } + return true; + })(); + """ + for _ in 0..<60 { + let ready = try await webView.evaluateJavaScript(js) as? Bool + if ready == true { return } + try await Task.sleep(nanoseconds: 50_000_000) + } + } + private func takeSnapshotAsync(webView: WKWebView, configuration: WKSnapshotConfiguration) async throws -> UIImage { try await withCheckedThrowingContinuation { cont in webView.takeSnapshot(with: configuration) { image, error in @@ -136,3 +155,4 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { navigationContinuation = nil } } + From 3f0c1943e2bc0f692123dbcfd32cf02875e3d806 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 11:09:36 +0100 Subject: [PATCH 14/20] code Signed-off-by: Marino Faggiana --- iOSClient/Utility/NCSVGRenderer.swift | 45 ++++++++------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/iOSClient/Utility/NCSVGRenderer.swift b/iOSClient/Utility/NCSVGRenderer.swift index 35b57b569d..be88278e0c 100644 --- a/iOSClient/Utility/NCSVGRenderer.swift +++ b/iOSClient/Utility/NCSVGRenderer.swift @@ -64,7 +64,7 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { """ try await loadHTMLAsync(webView: webView, html: html) - try await waitForSVGReady(webView: webView) + try await waitForImageReady(webView: webView) let config = WKSnapshotConfiguration() config.rect = CGRect(origin: .zero, size: logicalSize) @@ -82,6 +82,13 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { } private func loadHTMLAsync(webView: WKWebView, html: String) async throws { + // Cancel any in-flight load to avoid overlapping delegates/continuations + webView.stopLoading() + if let pending = navigationContinuation { + pending.resume(throwing: NSError(domain: "NCSVGRenderer", code: -22, userInfo: [NSLocalizedDescriptionKey: "Cancelled previous load"])) + navigationContinuation = nil + } + try await withCheckedThrowingContinuation { cont in navigationContinuation = cont webView.loadHTMLString(html, baseURL: nil) @@ -97,36 +104,13 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { })(); """ - // wait max 3 sec. - for _ in 0..<60 { + // wait max ~3 sec + for _ in 0..<100 { let ready = try await webView.evaluateJavaScript(js) as? Bool if ready == true { return } - try await Task.sleep(nanoseconds: 50_000_000) - } - } - - private func waitForSVGReady(webView: WKWebView) async throws { - let js = """ - (function() { - const svg = document.querySelector('#svgRoot') || document.querySelector('svg'); - if (!svg) return false; - try { - if (svg.getBBox) { - const bb = svg.getBBox(); - return bb && bb.width > 0 && bb.height > 0; - } - } catch (e) { - // Some SVGs may throw on getBBox; consider ready if the element exists - return true; - } - return true; - })(); - """ - for _ in 0..<60 { - let ready = try await webView.evaluateJavaScript(js) as? Bool - if ready == true { return } - try await Task.sleep(nanoseconds: 50_000_000) + try await Task.sleep(nanoseconds: 30_000_000) } + throw NSError(domain: "NCSVGRenderer", code: -24, userInfo: [NSLocalizedDescriptionKey: "Image not ready within timeout"]) } private func takeSnapshotAsync(webView: WKWebView, configuration: WKSnapshotConfiguration) async throws -> UIImage { @@ -148,11 +132,8 @@ final class NCSVGRenderer: NSObject, WKNavigationDelegate { navigationContinuation = nil } - func webView(_ webView: WKWebView, - didFail navigation: WKNavigation!, - withError error: Error) { + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { navigationContinuation?.resume(throwing: error) navigationContinuation = nil } } - From ebd55fbef6b72f8469af004f24801e53dabcd0c4 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 16:38:12 +0100 Subject: [PATCH 15/20] widget Signed-off-by: Marino Faggiana --- Widget/Dashboard/DashboardData.swift | 245 +++++++----------- .../Dashboard/DashboardWidgetProvider.swift | 8 +- Widget/Dashboard/DashboardWidgetView.swift | 4 +- Widget/Files/FilesData.swift | 20 +- 4 files changed, 117 insertions(+), 160 deletions(-) diff --git a/Widget/Dashboard/DashboardData.swift b/Widget/Dashboard/DashboardData.swift index 467ecf9eab..19252d4e41 100644 --- a/Widget/Dashboard/DashboardData.swift +++ b/Widget/Dashboard/DashboardData.swift @@ -53,16 +53,16 @@ struct DashboardData: Identifiable, Hashable { } let dashboardDatasTest: [DashboardData] = [ - .init(id: 0, title: "title0", subTitle: "subTitle-description0", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 1, title: "title1", subTitle: "subTitle-description1", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 2, title: "title2", subTitle: "subTitle-description2", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 3, title: "title3", subTitle: "subTitle-description3", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 4, title: "title4", subTitle: "subTitle-description4", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 5, title: "title5", subTitle: "subTitle-description5", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 6, title: "title6", subTitle: "subTitle-description6", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 7, title: "title7", subTitle: "subTitle-description7", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 8, title: "title8", subTitle: "subTitle-description8", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil), - .init(id: 9, title: "title9", subTitle: "subTitle-description9", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(named: "widget")!, template: true, avatar: false, imageColor: nil) + .init(id: 0, title: "title0", subTitle: "subTitle-description0", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 1, title: "title1", subTitle: "subTitle-description1", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 2, title: "title2", subTitle: "subTitle-description2", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 3, title: "title3", subTitle: "subTitle-description3", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 4, title: "title4", subTitle: "subTitle-description4", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 5, title: "title5", subTitle: "subTitle-description5", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 6, title: "title6", subTitle: "subTitle-description6", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 7, title: "title7", subTitle: "subTitle-description7", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 8, title: "title8", subTitle: "subTitle-description8", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), + .init(id: 9, title: "title9", subTitle: "subTitle-description9", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil) ] func getDashboardItems(displaySize: CGSize, withButton: Bool) -> Int { @@ -75,36 +75,7 @@ func getDashboardItems(displaySize: CGSize, withButton: Bool) -> Int { } } -func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?, user: String) async -> UIImage? { - guard let data = data else { - return nil - } - var imageData: UIImage? - let utilityFileSystem = NCUtilityFileSystem() - - if let image = UIImage(data: data), let image = image.resizeImage(size: size) { - imageData = image - } else { - do { - imageData = try await NCSVGRenderer().renderSVGToUIImage( - svgData: data, - fileName: fileNameToWrite - ) - } catch { - print("Unsupported image format: \(error.localizedDescription)") - } - } - - if let fileName = fileNameToWrite, let image = imageData { - do { - let fileNamePath: String = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName + ".png") - try image.pngData()?.write(to: URL(fileURLWithPath: fileNamePath), options: .atomic) - } catch { } - } - return imageData -} - -func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, displaySize: CGSize, completion: @escaping (_ entry: DashboardDataEntry) -> Void) { +func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, displaySize: CGSize) async -> DashboardDataEntry { let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() let dashboardItems = getDashboardItems(displaySize: displaySize, withButton: false) @@ -112,14 +83,15 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis var activeTableAccount: tableAccount? let versionApp = NCUtility().getVersionMaintenance() - if let groupDefaults = UserDefaults(suiteName: NCBrandOptions.shared.capabilitiesGroup), + guard let groupDefaults = UserDefaults(suiteName: NCBrandOptions.shared.capabilitiesGroup), let lastVersion = groupDefaults.string(forKey: NCGlobal.shared.udLastVersion), - lastVersion != versionApp { - return completion(DashboardDataEntry(date: Date(), datas: datasPlaceholder, dashboard: nil, buttons: nil, isPlaceholder: true, isEmpty: false, titleImage: UIImage(named: "widget")!, title: "Dashboard", footerImage: "checkmark.icloud", footerText: NSLocalizedString("_version_mismatch_error_", comment: ""), account: "")) + lastVersion == versionApp else { + return (DashboardDataEntry(date: Date(), datas: datasPlaceholder, dashboard: nil, buttons: nil, isPlaceholder: true, isEmpty: false, titleImage: UIImage(systemName: "circle.fill") ?? UIImage(), title: "Dashboard", footerImage: "checkmark.icloud", footerText: NSLocalizedString("_version_mismatch_error_", comment: ""), account: "")) } - if isPreview { - return completion(DashboardDataEntry(date: Date(), datas: datasPlaceholder, dashboard: nil, buttons: nil, isPlaceholder: true, isEmpty: false, titleImage: UIImage(named: "widget")!, title: "Dashboard", footerImage: "checkmark.icloud", footerText: NCBrandOptions.shared.brand + " dashboard", account: "")) + if isPreview, + let image = UIImage(systemName: "circle.fill") { + return (DashboardDataEntry(date: Date(), datas: datasPlaceholder, dashboard: nil, buttons: nil, isPlaceholder: true, isEmpty: false, titleImage: image, title: "Dashboard", footerImage: "checkmark.icloud", footerText: NCBrandOptions.shared.brand + " dashboard", account: "")) } let accountIdentifier: String = configuration?.accounts?.identifier ?? "active" @@ -130,22 +102,14 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis } guard let activeTableAccount else { - return completion(DashboardDataEntry(date: Date(), datas: datasPlaceholder, dashboard: nil, buttons: nil, isPlaceholder: true, isEmpty: false, titleImage: UIImage(named: "widget")!, title: "Dashboard", footerImage: "xmark.icloud", footerText: NSLocalizedString("_no_active_account_", comment: ""), account: "")) + return (DashboardDataEntry(date: Date(), datas: datasPlaceholder, dashboard: nil, buttons: nil, isPlaceholder: true, isEmpty: false, titleImage: UIImage(systemName: "circle.fill") ?? UIImage(), title: "Dashboard", footerImage: "xmark.icloud", footerText: NSLocalizedString("_no_active_account_", comment: ""), account: "")) } - - // Default widget - let result = NCManageDatabase.shared.getDashboardWidgetApplications(account: activeTableAccount.account).first - let id: String = configuration?.applications?.identifier ?? (result?.id ?? "recommendations") - - // NETWORKING - let password = NCPreferences().getPassword(account: activeTableAccount.account) - NextcloudKit.shared.setup(groupIdentifier: NCBrandOptions.shared.capabilitiesGroup, delegate: NCNetworking.shared) NextcloudKit.shared.appendSession(account: activeTableAccount.account, urlBase: activeTableAccount.urlBase, user: activeTableAccount.user, userId: activeTableAccount.userId, - password: password, + password: NCPreferences().getPassword(account: activeTableAccount.account), userAgent: userAgent, httpMaximumConnectionsPerHost: NCBrandOptions.shared.httpMaximumConnectionsPerHost, httpMaximumConnectionsPerHostInDownload: NCBrandOptions.shared.httpMaximumConnectionsPerHostInDownload, @@ -154,115 +118,106 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis // LOG let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, utility.getVersionBuild()) - NextcloudKit.configureLogger(logLevel: (NCBrandOptions.shared.disable_log ? .disabled : NCPreferences().log)) - nkLog(debug: "Start \(NCBrandOptions.shared.brand) dashboard widget session " + versionNextcloudiOS) - let (tableDashboard, tableButton) = NCManageDatabase.shared.getDashboardWidget(account: activeTableAccount.account, id: id) + // Widget + let widgetApplication = NCManageDatabase.shared.getDashboardWidgetApplications(account: activeTableAccount.account).first + let widgetApplicationId: String = configuration?.applications?.identifier ?? (widgetApplication?.id ?? "recommendations") + + let (tableDashboard, tableButton) = NCManageDatabase.shared.getDashboardWidget(account: activeTableAccount.account, id: widgetApplicationId) let existsButton = (tableButton?.isEmpty ?? true) ? false : true - let title = tableDashboard?.title ?? id + let title = tableDashboard?.title ?? widgetApplicationId - var imagetmp = UIImage(named: "widget")! + var titleImage: UIImage = UIImage(systemName: "circle.fill") ?? UIImage() if let fileName = tableDashboard?.iconClass { let fileNamePath: String = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName + ".png") if let image = UIImage(contentsOfFile: fileNamePath) { - imagetmp = image.withTintColor(NCBrandColor.shared.iconImageColor, renderingMode: .alwaysOriginal) + titleImage = image.withTintColor(NCBrandColor.shared.iconImageColor, renderingMode: .alwaysOriginal) } } - let titleImage = imagetmp - let options = NKRequestOptions(timeout: 90, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) - NextcloudKit.shared.getDashboardWidgetsApplication(id, account: activeTableAccount.account, options: options) { account, results, responseData, error in - Task { - var datas = [DashboardData]() - var numberItems = 0 - - if let results = results { - for result in results { - if let items = result.items { - numberItems = result.items?.count ?? 0 - var counter: Int = 0 - let dashboardItems = getDashboardItems(displaySize: displaySize, withButton: existsButton) - for item in items { - counter += 1 - let title = item.title ?? "" - let subtitle = item.subtitle ?? "" - var link = URL(string: "https://")! - if let entryLink = item.link, let url = URL(string: entryLink) { link = url } - var icon = UIImage(named: "file")! - var iconFileName: String? - - var imageTemplate: Bool = false - var imageAvatar: Bool = false - var imageColorized: Bool = false - var imageColor: UIColor? - - if let iconUrl = item.iconUrl, let url = URL(string: iconUrl) { - if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) { - - let path = (urlComponents.path as NSString) - let pathComponents = path.components(separatedBy: "/") - let queryItems = urlComponents.queryItems - - if (pathComponents.last as? NSString)?.pathExtension.lowercased() == "svg" { - imageTemplate = true - } - if let item = queryItems?.filter({ $0.name == "fileId" }).first?.value { - iconFileName = item - } else if pathComponents.contains("avatar") { - iconFileName = pathComponents[pathComponents.count - 2] - imageAvatar = true - } else if pathComponents.contains("getCalendarDotSvg") { - imageColorized = true - } else { - iconFileName = ((path.lastPathComponent) as NSString).deletingPathExtension - } - } - // Color - if imageColorized, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) { - let path = (urlComponents.path as NSString) - let colorString = ((path.lastPathComponent) as NSString).deletingPathExtension - imageColor = UIColor(hex: colorString) - icon = utility.loadImage(named: "circle.fill") - } else if let fileName = iconFileName { - let fileNamePath: String = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileName + ".png") - if FileManager().fileExists(atPath: fileNamePath), let image = UIImage(contentsOfFile: fileNamePath) { - icon = image - } else { - let (_, _, error) = await NextcloudKit.shared.downloadPreviewAsync(url: url, account: activeTableAccount.account) - if error == .success, - let data = responseData?.data, - let image = await convertDataToImage(data: data, size: NCGlobal.shared.size256, fileNameToWrite: fileName, user: activeTableAccount.userId) { - icon = image - } - } + let resultsDashboardWidget = await NextcloudKit.shared.getDashboardWidgetsApplicationAsync(widgetApplicationId, account: activeTableAccount.account, options: options) + + var datas = [DashboardData]() + var numberItems = 0 + + if resultsDashboardWidget.error == .success, + let dashboardApplications = resultsDashboardWidget.dashboardApplications { + for dashboardApplication in dashboardApplications { + if let items = dashboardApplication.items { + numberItems = dashboardApplication.items?.count ?? 0 + var counter: Int = 0 + let dashboardItems = getDashboardItems(displaySize: displaySize, withButton: existsButton) + for item in items { + counter += 1 + + let title = item.title ?? "" + let subtitle = item.subtitle ?? "" + + var link: URL = URL(string: "https://")! + if let entryLink = item.link, + let url = URL(string: entryLink) { + link = url + } + var iconImage = UIImage(systemName: "circle.fill") ?? UIImage() + + var imageTemplate: Bool = false + var imageAvatar: Bool = false + var imageColorized: Bool = false + var imageColor: UIColor? + + if let iconUrl = item.iconUrl, let url = URL(string: iconUrl) { + if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) { + let path = (urlComponents.path as NSString) + let pathComponents = path.components(separatedBy: "/") + + if (pathComponents.last as? NSString)?.pathExtension.lowercased() == "svg" { + imageTemplate = true + } else if pathComponents.contains("avatar") { + imageAvatar = true + } else if pathComponents.contains("getCalendarDotSvg") { + imageColorized = true + } + } + // Color + if imageColorized, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) { + let path = (urlComponents.path as NSString) + let colorString = ((path.lastPathComponent) as NSString).deletingPathExtension + imageColor = UIColor(hex: colorString) + } else { + let results = await NextcloudKit.shared.downloadPreviewAsync(url: url, account: activeTableAccount.account) + if results.error == .success, + let data = results.responseData?.data { + if let image = UIImage(data: data) { + iconImage = image + } else if let image = try? await NCSVGRenderer().renderSVGToUIImage(svgData: data) { + iconImage = image } } - - let data = DashboardData(id: counter, title: title, subTitle: subtitle, link: link, icon: icon, template: imageTemplate, avatar: imageAvatar, imageColor: imageColor) - datas.append(data) - - if datas.count == dashboardItems { break } } } - } - } - - var buttons = tableButton - if numberItems == datas.count, let tableButton = tableButton, tableButton.contains(where: { $0.type == "more"}) { - buttons = tableButton.filter(({ $0.type != "more" })) - } - let alias = (activeTableAccount.alias.isEmpty) ? "" : (" (" + activeTableAccount.alias + ")") - let footerText = "Dashboard " + NSLocalizedString("_of_", comment: "") + " " + activeTableAccount.displayName + alias + let data = DashboardData(id: counter, title: title, subTitle: subtitle, link: link, icon: iconImage, template: imageTemplate, avatar: imageAvatar, imageColor: imageColor) + datas.append(data) - if error != .success { - completion(DashboardDataEntry(date: Date(), datas: datasPlaceholder, dashboard: tableDashboard, buttons: buttons, isPlaceholder: true, isEmpty: false, titleImage: titleImage, title: title, footerImage: "xmark.icloud", footerText: error.errorDescription, account: account)) - } else { - completion(DashboardDataEntry(date: Date(), datas: datas, dashboard: tableDashboard, buttons: buttons, isPlaceholder: false, isEmpty: datas.isEmpty, titleImage: titleImage, title: title, footerImage: "checkmark.icloud", footerText: footerText, account: account)) + if datas.count == dashboardItems { break } + } } } } + var buttons = tableButton + if numberItems == datas.count, let tableButton = tableButton, tableButton.contains(where: { $0.type == "more"}) { + buttons = tableButton.filter(({ $0.type != "more" })) + } + + let alias = (activeTableAccount.alias.isEmpty) ? "" : (" (" + activeTableAccount.alias + ")") + let footerText = "Dashboard " + NSLocalizedString("_of_", comment: "") + " " + activeTableAccount.displayName + alias + + if resultsDashboardWidget.error != .success { + return(DashboardDataEntry(date: Date(), datas: datasPlaceholder, dashboard: tableDashboard, buttons: buttons, isPlaceholder: true, isEmpty: false, titleImage: titleImage, title: title, footerImage: "xmark.icloud", footerText: resultsDashboardWidget.error.errorDescription, account: activeTableAccount.account)) + } else { + return(DashboardDataEntry(date: Date(), datas: datas, dashboard: tableDashboard, buttons: buttons, isPlaceholder: false, isEmpty: datas.isEmpty, titleImage: titleImage, title: title, footerImage: "checkmark.icloud", footerText: footerText, account: activeTableAccount.account)) + } } diff --git a/Widget/Dashboard/DashboardWidgetProvider.swift b/Widget/Dashboard/DashboardWidgetProvider.swift index 419bedda6c..7e62b5b63c 100644 --- a/Widget/Dashboard/DashboardWidgetProvider.swift +++ b/Widget/Dashboard/DashboardWidgetProvider.swift @@ -34,18 +34,20 @@ struct DashboardWidgetProvider: IntentTimelineProvider { let dashboardItems = getDashboardItems(displaySize: context.displaySize, withButton: false) let datasPlaceholder = Array(dashboardDatasTest[0...dashboardItems]) let title = "Dashboard" - let titleImage = UIImage(named: "widget")! + let titleImage = UIImage(systemName: "circle.fill") ?? UIImage() return Entry(date: Date(), datas: datasPlaceholder, dashboard: nil, buttons: nil, isPlaceholder: true, isEmpty: false, titleImage: titleImage, title: title, footerImage: "checkmark.icloud", footerText: NCBrandOptions.shared.brand + " widget", account: "") } func getSnapshot(for configuration: DashboardIntent, in context: Context, completion: @escaping (DashboardDataEntry) -> Void) { - getDashboardDataEntry(configuration: configuration, isPreview: false, displaySize: context.displaySize) { entry in + Task { + let entry = await getDashboardDataEntry(configuration: configuration, isPreview: false, displaySize: context.displaySize) completion(entry) } } func getTimeline(for configuration: DashboardIntent, in context: Context, completion: @escaping (Timeline) -> Void) { - getDashboardDataEntry(configuration: configuration, isPreview: context.isPreview, displaySize: context.displaySize) { entry in + Task { + let entry = await getDashboardDataEntry(configuration: configuration, isPreview: context.isPreview, displaySize: context.displaySize) let timeLine = Timeline(entries: [entry], policy: .atEnd) completion(timeLine) } diff --git a/Widget/Dashboard/DashboardWidgetView.swift b/Widget/Dashboard/DashboardWidgetView.swift index f7cc722054..475f776c48 100644 --- a/Widget/Dashboard/DashboardWidgetView.swift +++ b/Widget/Dashboard/DashboardWidgetView.swift @@ -197,8 +197,8 @@ struct DashboardWidget_Previews: PreviewProvider { static var previews: some View { let datas = Array(dashboardDatasTest[0...4]) let title = "Dashboard" - let titleImage = UIImage(named: "widget")! - let entry = DashboardDataEntry(date: Date(), datas: datas, dashboard: nil, buttons: nil, isPlaceholder: false, isEmpty: true, titleImage: titleImage, title: title, footerImage: "checkmark.icloud", footerText: "Nextcloud widget", account: "") + let titleImage = UIImage(systemName: "circle.fill")! + let entry = DashboardDataEntry(date: Date(), datas: datas, dashboard: nil, buttons: nil, isPlaceholder: false, isEmpty: true, titleImage: titleImage, title: title, footerImage: "checkmark.icloud", footerText: NCBrandOptions.shared.brand + " widget", account: "") DashboardWidgetView(entry: entry).previewContext(WidgetPreviewContext(family: .systemLarge)) } } diff --git a/Widget/Files/FilesData.swift b/Widget/Files/FilesData.swift index a93244b189..6c8a47434b 100644 --- a/Widget/Files/FilesData.swift +++ b/Widget/Files/FilesData.swift @@ -49,16 +49,16 @@ struct FilesData: Identifiable, Hashable { } let filesDatasTest: [FilesData] = [ - .init(id: "0", image: UIImage(named: "widget")!, title: "title1", subTitle: "subTitle-description1", url: URL(string: "https://nextcloud.com/")!), - .init(id: "1", image: UIImage(named: "widget")!, title: "title2", subTitle: "subTitle-description2", url: URL(string: "https://nextcloud.com/")!), - .init(id: "2", image: UIImage(named: "widget")!, title: "title3", subTitle: "subTitle-description3", url: URL(string: "https://nextcloud.com/")!), - .init(id: "3", image: UIImage(named: "widget")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), - .init(id: "4", image: UIImage(named: "widget")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), - .init(id: "5", image: UIImage(named: "widget")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), - .init(id: "6", image: UIImage(named: "widget")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), - .init(id: "7", image: UIImage(named: "widget")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), - .init(id: "8", image: UIImage(named: "widget")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), - .init(id: "9", image: UIImage(named: "widget")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!) + .init(id: "0", image: UIImage(systemName: "circle.fill")!, title: "title1", subTitle: "subTitle-description1", url: URL(string: "https://nextcloud.com/")!), + .init(id: "1", image: UIImage(systemName: "circle.fill")!, title: "title2", subTitle: "subTitle-description2", url: URL(string: "https://nextcloud.com/")!), + .init(id: "2", image: UIImage(systemName: "circle.fill")!, title: "title3", subTitle: "subTitle-description3", url: URL(string: "https://nextcloud.com/")!), + .init(id: "3", image: UIImage(systemName: "circle.fill")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), + .init(id: "4", image: UIImage(systemName: "circle.fill")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), + .init(id: "5", image: UIImage(systemName: "circle.fill")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), + .init(id: "6", image: UIImage(systemName: "circle.fill")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), + .init(id: "7", image: UIImage(systemName: "circle.fill")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), + .init(id: "8", image: UIImage(systemName: "circle.fill")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!), + .init(id: "9", image: UIImage(systemName: "circle.fill")!, title: "title4", subTitle: "subTitle-description4", url: URL(string: "https://nextcloud.com/")!) ] func getTitleFilesWidget(tableAccount: tableAccount?) -> String { From 17248ad8c03f069b7133b93d58ac4474515f99ef Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 16:52:43 +0100 Subject: [PATCH 16/20] code Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCService.swift | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index 1b52e8f933..7f5b772a63 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -41,6 +41,9 @@ class NCService: NSObject { // Start a background synchronization task await synchronize(account: account) + + // Fetch and display dashboard widgets if available + await requestDashboardWidget(account: account) } } @@ -119,6 +122,40 @@ class NCService: NSObject { } } + private func requestDashboardWidget(account: String) async { + let resultsDashboardWidget = await NextcloudKit.shared.getDashboardWidgetAsync(account: account) + if resultsDashboardWidget.error == .success, + let dashboardWidgets = resultsDashboardWidget.dashboardWidgets { + await NCManageDatabase.shared.addDashboardWidgetAsync(account: account, dashboardWidgets: dashboardWidgets) + for widget in dashboardWidgets { + if let url = URL(string: widget.iconUrl), + let fileName = widget.iconClass { + let resultsDownloadPreview = await NextcloudKit.shared.downloadPreviewAsync(url: url, account: account) + if resultsDownloadPreview.error == .success, + let data = resultsDownloadPreview.responseData?.data { + var image: UIImage? + let size = CGSize(width: 256, height: 256) + + if let uiImage = UIImage(data: data)?.resizeImage(size: size) { + image = uiImage + } else if let svgImage = try? await NCSVGRenderer().renderSVGToUIImage(svgData: data, size: size) { + image = svgImage + } + + if let image { + let filePath = (self.utilityFileSystem.directoryUserData as NSString).appendingPathComponent(fileName + ".png") + do { + try image.pngData()?.write(to: URL(fileURLWithPath: filePath), options: .atomic) + } catch { + print("Failed to write image to disk: \(error.localizedDescription)") + } + } + } + } + } + } + } + private func requestServerCapabilities(account: String, controller: NCMainTabBarController?) async { let resultsCapabilities = await NextcloudKit.shared.getCapabilitiesAsync(account: account) { task in Task { From 99ed19bf014c404022ae5a026108cc0bc453b829 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 17:13:55 +0100 Subject: [PATCH 17/20] code Signed-off-by: Marino Faggiana --- Widget/Dashboard/DashboardData.swift | 53 +++++------------ .../Dashboard/DashboardWidgetProvider.swift | 27 ++------- Widget/Dashboard/DashboardWidgetView.swift | 57 ++----------------- Widget/Files/FilesData.swift | 25 +------- Widget/Files/FilesWidgetProvider.swift | 25 +------- Widget/Files/FilesWidgetView.swift | 31 ++-------- Widget/Lockscreen/LockscreenData.swift | 25 +------- .../Lockscreen/LockscreenWidgetProvider.swift | 25 +------- Widget/Lockscreen/LockscreenWidgetView.swift | 25 +------- Widget/Toolbar/ToolbarData.swift | 25 +------- Widget/Toolbar/ToolbarWidgetProvider.swift | 25 +------- Widget/Toolbar/ToolbarWidgetView.swift | 25 +------- Widget/Widget.swift | 26 +-------- 13 files changed, 54 insertions(+), 340 deletions(-) diff --git a/Widget/Dashboard/DashboardData.swift b/Widget/Dashboard/DashboardData.swift index 19252d4e41..b37f4259f0 100644 --- a/Widget/Dashboard/DashboardData.swift +++ b/Widget/Dashboard/DashboardData.swift @@ -1,25 +1,6 @@ -// -// DashboardData.swift -// Widget -// -// Created by Marino Faggiana on 20/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import WidgetKit @@ -47,22 +28,21 @@ struct DashboardData: Identifiable, Hashable { let subTitle: String let link: URL let icon: UIImage - let template: Bool let avatar: Bool let imageColor: UIColor? } let dashboardDatasTest: [DashboardData] = [ - .init(id: 0, title: "title0", subTitle: "subTitle-description0", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 1, title: "title1", subTitle: "subTitle-description1", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 2, title: "title2", subTitle: "subTitle-description2", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 3, title: "title3", subTitle: "subTitle-description3", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 4, title: "title4", subTitle: "subTitle-description4", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 5, title: "title5", subTitle: "subTitle-description5", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 6, title: "title6", subTitle: "subTitle-description6", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 7, title: "title7", subTitle: "subTitle-description7", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 8, title: "title8", subTitle: "subTitle-description8", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil), - .init(id: 9, title: "title9", subTitle: "subTitle-description9", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, template: true, avatar: false, imageColor: nil) + .init(id: 0, title: "title0", subTitle: "subTitle-description0", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 1, title: "title1", subTitle: "subTitle-description1", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 2, title: "title2", subTitle: "subTitle-description2", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 3, title: "title3", subTitle: "subTitle-description3", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 4, title: "title4", subTitle: "subTitle-description4", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 5, title: "title5", subTitle: "subTitle-description5", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 6, title: "title6", subTitle: "subTitle-description6", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 7, title: "title7", subTitle: "subTitle-description7", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 8, title: "title8", subTitle: "subTitle-description8", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil), + .init(id: 9, title: "title9", subTitle: "subTitle-description9", link: URL(string: "https://nextcloud.com/")!, icon: UIImage(systemName: "circle.fill")!, avatar: false, imageColor: nil) ] func getDashboardItems(displaySize: CGSize, withButton: Bool) -> Int { @@ -162,7 +142,6 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis } var iconImage = UIImage(systemName: "circle.fill") ?? UIImage() - var imageTemplate: Bool = false var imageAvatar: Bool = false var imageColorized: Bool = false var imageColor: UIColor? @@ -172,9 +151,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis let path = (urlComponents.path as NSString) let pathComponents = path.components(separatedBy: "/") - if (pathComponents.last as? NSString)?.pathExtension.lowercased() == "svg" { - imageTemplate = true - } else if pathComponents.contains("avatar") { + if pathComponents.contains("avatar") { imageAvatar = true } else if pathComponents.contains("getCalendarDotSvg") { imageColorized = true @@ -198,7 +175,7 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis } } - let data = DashboardData(id: counter, title: title, subTitle: subtitle, link: link, icon: iconImage, template: imageTemplate, avatar: imageAvatar, imageColor: imageColor) + let data = DashboardData(id: counter, title: title, subTitle: subtitle, link: link, icon: iconImage, avatar: imageAvatar, imageColor: imageColor) datas.append(data) if datas.count == dashboardItems { break } diff --git a/Widget/Dashboard/DashboardWidgetProvider.swift b/Widget/Dashboard/DashboardWidgetProvider.swift index 7e62b5b63c..60169bbf6c 100644 --- a/Widget/Dashboard/DashboardWidgetProvider.swift +++ b/Widget/Dashboard/DashboardWidgetProvider.swift @@ -1,25 +1,6 @@ -// -// DashboardWidgetProvider.swift -// Widget -// -// Created by Marino Faggiana on 25/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import WidgetKit @@ -34,7 +15,7 @@ struct DashboardWidgetProvider: IntentTimelineProvider { let dashboardItems = getDashboardItems(displaySize: context.displaySize, withButton: false) let datasPlaceholder = Array(dashboardDatasTest[0...dashboardItems]) let title = "Dashboard" - let titleImage = UIImage(systemName: "circle.fill") ?? UIImage() + let titleImage = UIImage(systemName: "circle.fill") ?? UIImage() return Entry(date: Date(), datas: datasPlaceholder, dashboard: nil, buttons: nil, isPlaceholder: true, isEmpty: false, titleImage: titleImage, title: title, footerImage: "checkmark.icloud", footerText: NCBrandOptions.shared.brand + " widget", account: "") } diff --git a/Widget/Dashboard/DashboardWidgetView.swift b/Widget/Dashboard/DashboardWidgetView.swift index 475f776c48..61eb4138be 100644 --- a/Widget/Dashboard/DashboardWidgetView.swift +++ b/Widget/Dashboard/DashboardWidgetView.swift @@ -1,25 +1,6 @@ -// -// DashboardWidgetView.swift -// Widget -// -// Created by Marino Faggiana on 20/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import SwiftUI import WidgetKit @@ -61,21 +42,13 @@ struct DashboardWidgetView: View { } .frame(width: geo.size.width - 20) .padding([.top, .leading, .trailing], 10) - if !entry.isEmpty { - VStack(alignment: .leading) { - VStack(spacing: 0) { - ForEach(entry.datas, id: \.id) { element in - Link(destination: element.link) { - HStack { - let subTitleColor = Color(white: 0.5) - if entry.isPlaceholder { Circle() .fill(Color(.systemGray4)) @@ -86,26 +59,6 @@ struct DashboardWidgetView: View { .resizable() .frame(width: 20, height: 20) .foregroundColor(Color(color)) - } else if element.template { - if entry.dashboard?.itemIconsRound ?? false { - Image(uiImage: element.icon) - .renderingMode(.template) - .resizable() - .scaledToFill() - .frame(width: 20, height: 20) - .foregroundColor(.white) - .padding(8) - .background(Color(.systemGray4)) - .clipShape(Circle()) - } else { - Image(uiImage: element.icon) - .renderingMode(.template) - .resizable() - .scaledToFill() - .frame(width: 25, height: 25) - .clipped() - .cornerRadius(5) - } } else { if entry.dashboard?.itemIconsRound ?? false || element.avatar { Image(uiImage: element.icon) @@ -150,7 +103,6 @@ struct DashboardWidgetView: View { } if let buttons = entry.buttons, !buttons.isEmpty, !entry.isPlaceholder { - HStack(spacing: 10) { let brandColor = Color(NCBrandColor.shared.getElement(account: entry.account)) let brandTextColor = Color(NCBrandColor.shared.getText(account: entry.account)) @@ -172,7 +124,6 @@ struct DashboardWidgetView: View { } HStack { - Image(systemName: entry.footerImage) .resizable() .scaledToFit() @@ -189,7 +140,7 @@ struct DashboardWidgetView: View { .frame(maxWidth: geo.size.width, maxHeight: geo.size.height - 2, alignment: .bottomTrailing) } } - .widgetBackground(Color(UIColor.systemBackground)) + .containerBackground(.background, for: .widget) } } diff --git a/Widget/Files/FilesData.swift b/Widget/Files/FilesData.swift index 6c8a47434b..2ad939f49a 100644 --- a/Widget/Files/FilesData.swift +++ b/Widget/Files/FilesData.swift @@ -1,25 +1,6 @@ -// -// FilesData.swift -// Widget -// -// Created by Marino Faggiana on 25/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import WidgetKit diff --git a/Widget/Files/FilesWidgetProvider.swift b/Widget/Files/FilesWidgetProvider.swift index d49e360709..fdee4b6196 100644 --- a/Widget/Files/FilesWidgetProvider.swift +++ b/Widget/Files/FilesWidgetProvider.swift @@ -1,25 +1,6 @@ -// -// FilesWidgetProvider.swift -// Widget -// -// Created by Marino Faggiana on 25/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import WidgetKit diff --git a/Widget/Files/FilesWidgetView.swift b/Widget/Files/FilesWidgetView.swift index 1db7624e5d..0dde0acc7c 100644 --- a/Widget/Files/FilesWidgetView.swift +++ b/Widget/Files/FilesWidgetView.swift @@ -1,35 +1,13 @@ -// -// FilesWidgetView.swift -// Widget -// -// Created by Marino Faggiana on 25/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import SwiftUI import WidgetKit struct FilesWidgetView: View { - var entry: FilesDataEntry - var body: some View { - let parameterLink = "&user=\(entry.userId)&url=\(entry.url)" let linkNoAction: URL = URL(string: NCGlobal.shared.widgetActionNoAction + parameterLink) != nil ? URL(string: NCGlobal.shared.widgetActionNoAction + parameterLink)! : URL(string: NCGlobal.shared.widgetActionNoAction)! let linkActionUploadAsset: URL = URL(string: NCGlobal.shared.widgetActionUploadAsset + parameterLink) != nil ? URL(string: NCGlobal.shared.widgetActionUploadAsset + parameterLink)! : URL(string: NCGlobal.shared.widgetActionUploadAsset)! @@ -55,7 +33,6 @@ struct FilesWidgetView: View { } ZStack(alignment: .topLeading) { - HStack { Text(entry.tile) .font(.system(size: 12)) @@ -185,7 +162,7 @@ struct FilesWidgetView: View { .frame(maxWidth: geo.size.width, maxHeight: geo.size.height - 2, alignment: .bottomTrailing) } } - .widgetBackground(Color(UIColor.systemBackground)) + .containerBackground(.background, for: .widget) } } diff --git a/Widget/Lockscreen/LockscreenData.swift b/Widget/Lockscreen/LockscreenData.swift index f7b006854b..252799d770 100644 --- a/Widget/Lockscreen/LockscreenData.swift +++ b/Widget/Lockscreen/LockscreenData.swift @@ -1,25 +1,6 @@ -// -// LockscreenData.swift -// Widget -// -// Created by Marino Faggiana on 13/10/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import WidgetKit diff --git a/Widget/Lockscreen/LockscreenWidgetProvider.swift b/Widget/Lockscreen/LockscreenWidgetProvider.swift index 92949263fa..ee5c7effcb 100644 --- a/Widget/Lockscreen/LockscreenWidgetProvider.swift +++ b/Widget/Lockscreen/LockscreenWidgetProvider.swift @@ -1,25 +1,6 @@ -// -// LockscreenWidgetProvider.swift -// Widget -// -// Created by Marino Faggiana on 13/10/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import WidgetKit diff --git a/Widget/Lockscreen/LockscreenWidgetView.swift b/Widget/Lockscreen/LockscreenWidgetView.swift index b03e275441..661ef51487 100644 --- a/Widget/Lockscreen/LockscreenWidgetView.swift +++ b/Widget/Lockscreen/LockscreenWidgetView.swift @@ -1,25 +1,6 @@ -// -// LockscreenWidgetView.swift -// Widget -// -// Created by Marino Faggiana on 13/10/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import SwiftUI import WidgetKit diff --git a/Widget/Toolbar/ToolbarData.swift b/Widget/Toolbar/ToolbarData.swift index 8c8c32906b..0306f1d911 100644 --- a/Widget/Toolbar/ToolbarData.swift +++ b/Widget/Toolbar/ToolbarData.swift @@ -1,25 +1,6 @@ -// -// ToolbarData.swift -// Widget -// -// Created by Marino Faggiana on 25/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import WidgetKit diff --git a/Widget/Toolbar/ToolbarWidgetProvider.swift b/Widget/Toolbar/ToolbarWidgetProvider.swift index 0c72967f4e..f9bba1ba47 100644 --- a/Widget/Toolbar/ToolbarWidgetProvider.swift +++ b/Widget/Toolbar/ToolbarWidgetProvider.swift @@ -1,25 +1,6 @@ -// -// ToolbarWidgetProvider.swift -// Widget -// -// Created by Marino Faggiana on 25/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import WidgetKit diff --git a/Widget/Toolbar/ToolbarWidgetView.swift b/Widget/Toolbar/ToolbarWidgetView.swift index 1621ce5217..a2d903e168 100644 --- a/Widget/Toolbar/ToolbarWidgetView.swift +++ b/Widget/Toolbar/ToolbarWidgetView.swift @@ -1,25 +1,6 @@ -// -// ToolbarWidgetView.swift -// Widget -// -// Created by Marino Faggiana on 25/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import SwiftUI import WidgetKit diff --git a/Widget/Widget.swift b/Widget/Widget.swift index 25b4a96d03..469b8339d3 100644 --- a/Widget/Widget.swift +++ b/Widget/Widget.swift @@ -1,25 +1,6 @@ -// -// NextcloudWidget.swift -// NextcloudWidget -// -// Created by Marino Faggiana on 20/08/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// 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 . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import WidgetKit import Intents @@ -27,7 +8,6 @@ import SwiftUI @main struct NextcloudWidgetBundle: WidgetBundle { - @WidgetBundleBuilder var body: some Widget { DashboardWidget() From 496aa579701ef72269d8b208afa9ddae66d4f58f Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 17:50:44 +0100 Subject: [PATCH 18/20] code Signed-off-by: Marino Faggiana --- Widget/Files/FilesData.swift | 150 ++++++++++++------------- Widget/Files/FilesWidgetProvider.swift | 9 +- Widget/Files/FilesWidgetView.swift | 95 ++++++++-------- 3 files changed, 126 insertions(+), 128 deletions(-) diff --git a/Widget/Files/FilesData.swift b/Widget/Files/FilesData.swift index 2ad939f49a..12a31755bb 100644 --- a/Widget/Files/FilesData.swift +++ b/Widget/Files/FilesData.swift @@ -61,27 +61,22 @@ func getTitleFilesWidget(tableAccount: tableAccount?) -> String { } } -func getFilesItems(displaySize: CGSize) -> Int { - let items = Int((displaySize.height - 90) / 55) - return items -} - -func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySize: CGSize, completion: @escaping (_ entry: FilesDataEntry) -> Void) { +func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySize: CGSize) async -> FilesDataEntry { let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() - let filesItems = getFilesItems(displaySize: displaySize) - let datasPlaceholder = Array(filesDatasTest[0...filesItems - 1]) + let maxItems = 5 + let datasPlaceholder = Array(filesDatasTest[0...maxItems - 1]) var activeTableAccount: tableAccount? let versionApp = NCUtility().getVersionMaintenance() if let groupDefaults = UserDefaults(suiteName: NCBrandOptions.shared.capabilitiesGroup), let lastVersion = groupDefaults.string(forKey: NCGlobal.shared.udLastVersion), lastVersion != versionApp { - return completion(FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: "", url: "", account: "", tile: getTitleFilesWidget(tableAccount: nil), footerImage: "checkmark.icloud", footerText: NSLocalizedString("_version_mismatch_error_", comment: ""))) + return (FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: "", url: "", account: "", tile: getTitleFilesWidget(tableAccount: nil), footerImage: "checkmark.icloud", footerText: NSLocalizedString("_version_mismatch_error_", comment: ""))) } if isPreview { - return completion(FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: "", url: "", account: "", tile: getTitleFilesWidget(tableAccount: nil), footerImage: "checkmark.icloud", footerText: NCBrandOptions.shared.brand + " files")) + return (FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: "", url: "", account: "", tile: getTitleFilesWidget(tableAccount: nil), footerImage: "checkmark.icloud", footerText: NCBrandOptions.shared.brand + " files")) } let accountIdentifier: String = configuration?.accounts?.identifier ?? "active" @@ -92,18 +87,15 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi } guard let activeTableAccount else { - return completion(FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: "", url: "", account: "", tile: getTitleFilesWidget(tableAccount: nil), footerImage: "xmark.icloud", footerText: NSLocalizedString("_no_active_account_", value: "No account found", comment: ""))) + return (FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: "", url: "", account: "", tile: getTitleFilesWidget(tableAccount: nil), footerImage: "xmark.icloud", footerText: NSLocalizedString("_no_active_account_", value: "No account found", comment: ""))) } - // NETWORKING - let password = NCPreferences().getPassword(account: activeTableAccount.account) - NextcloudKit.shared.setup(groupIdentifier: NCBrandOptions.shared.capabilitiesGroup, delegate: NCNetworking.shared) NextcloudKit.shared.appendSession(account: activeTableAccount.account, urlBase: activeTableAccount.urlBase, user: activeTableAccount.user, userId: activeTableAccount.userId, - password: password, + password: NCPreferences().getPassword(account: activeTableAccount.account), userAgent: userAgent, httpMaximumConnectionsPerHost: NCBrandOptions.shared.httpMaximumConnectionsPerHost, httpMaximumConnectionsPerHostInDownload: NCBrandOptions.shared.httpMaximumConnectionsPerHostInDownload, @@ -170,82 +162,80 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi // LOG let versionNextcloudiOS = String(format: NCBrandOptions.shared.textCopyrightNextcloudiOS, utility.getVersionBuild()) - NextcloudKit.configureLogger(logLevel: (NCBrandOptions.shared.disable_log ? .disabled : NCPreferences().log)) - nkLog(debug: "Start \(NCBrandOptions.shared.brand) widget session " + versionNextcloudiOS) let options = NKRequestOptions(timeout: 30, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue) - NextcloudKit.shared.searchBodyRequest(serverUrl: activeTableAccount.urlBase, requestBody: requestBody, showHiddenFiles: showHiddenFiles, account: activeTableAccount.account, options: options) { _, files, data, error in - Task { - var datas: [FilesData] = [] - let title = getTitleFilesWidget(tableAccount: activeTableAccount) - let files = files?.sorted(by: { ($0.date as Date) > ($1.date as Date) }) ?? [] - - for file in files { - var useTypeIconFile = false - var image: UIImage? - - if file.directory || (!file.livePhotoFile.isEmpty && file.classFile == NKTypeClassFile.video.rawValue) { - continue - } - - // SUBTITLE - let subTitle = utility.getRelativeDateTitle(file.date as Date) + " · " + utilityFileSystem.transformedSize(file.size) - - // URL: nextcloud://open-file?path=Talk/IMG_0000123.jpg&user=marinofaggiana&link=https://cloud.nextcloud.com/f/123 - guard var path = utilityFileSystem.getPath(path: file.path, user: file.user, fileName: file.fileName).urlEncoded else { continue } - if path.first == "/" { path = String(path.dropFirst())} - guard let user = file.user.urlEncoded else { continue } - let link = file.urlBase + "/f/" + file.fileId - let urlString = "nextcloud://open-file?path=\(path)&user=\(user)&link=\(link)" - guard let url = URL(string: urlString) else { continue } - - // IMAGE + let results = await NextcloudKit.shared.searchBodyRequestAsync(serverUrl: activeTableAccount.urlBase, requestBody: requestBody, showHiddenFiles: showHiddenFiles, account: activeTableAccount.account, options: options) + + var datas: [FilesData] = [] + let title = getTitleFilesWidget(tableAccount: activeTableAccount) + let files = results.files?.sorted(by: { ($0.date as Date) > ($1.date as Date) }) ?? [] + + for file in files { + var useTypeIconFile = false + var image: UIImage? + + if file.directory || (!file.livePhotoFile.isEmpty && file.classFile == NKTypeClassFile.video.rawValue) { + continue + } + + // SUBTITLE + let subTitle = utility.getRelativeDateTitle(file.date as Date) + " · " + utilityFileSystem.transformedSize(file.size) + + // URL: nextcloud://open-file?path=Talk/IMG_0000123.jpg&user=marinofaggiana&link=https://cloud.nextcloud.com/f/123 + guard var path = utilityFileSystem.getPath(path: file.path, user: file.user, fileName: file.fileName).urlEncoded else { continue } + if path.first == "/" { path = String(path.dropFirst())} + guard let user = file.user.urlEncoded else { continue } + let link = file.urlBase + "/f/" + file.fileId + let urlString = "nextcloud://open-file?path=\(path)&user=\(user)&link=\(link)" + guard let url = URL(string: urlString) else { continue } + + // IMAGE + image = utility.getImage(ocId: file.ocId, + etag: file.etag, + ext: NCGlobal.shared.previewExt512, + userId: activeTableAccount.userId, + urlBase: activeTableAccount.urlBase) + if image == nil, file.hasPreview { + let result = await NextcloudKit.shared.downloadPreviewAsync(fileId: file.fileId, + etag: file.etag, + account: activeTableAccount.account, + options: options) + if result.error == .success, let data = result.responseData?.data { + utility.createImageFileFrom(data: data, + ocId: file.ocId, + etag: file.etag, + userId: activeTableAccount.userId, + urlBase: activeTableAccount.urlBase) image = utility.getImage(ocId: file.ocId, etag: file.etag, - ext: NCGlobal.shared.previewExt512, + ext: NCGlobal.shared.previewExt256, userId: activeTableAccount.userId, urlBase: activeTableAccount.urlBase) - if image == nil, file.hasPreview { - let result = await NextcloudKit.shared.downloadPreviewAsync(fileId: file.fileId, - etag: file.etag, - account: activeTableAccount.account, - options: options) - if result.error == .success, let data = result.responseData?.data { - utility.createImageFileFrom(data: data, - ocId: file.ocId, - etag: file.etag, - userId: activeTableAccount.userId, - urlBase: activeTableAccount.urlBase) - image = utility.getImage(ocId: file.ocId, - etag: file.etag, - ext: NCGlobal.shared.previewExt256, - userId: activeTableAccount.userId, - urlBase: activeTableAccount.urlBase) - } - } - if image == nil { - image = utility.loadImage(named: file.iconName, useTypeIconFile: true, account: file.account) - useTypeIconFile = true - } - - let metadata = await NCManageDatabaseCreateMetadata().convertFileToMetadataAsync(file) - - // DATA - let data = FilesData(id: metadata.ocId, image: image ?? UIImage(), title: metadata.fileNameView, subTitle: subTitle, url: url, useTypeIconFile: useTypeIconFile) - datas.append(data) - if datas.count == filesItems { break} } + } + if image == nil { + image = utility.loadImage(named: file.iconName, useTypeIconFile: true, account: file.account) + useTypeIconFile = true + } - let alias = (activeTableAccount.alias.isEmpty) ? "" : (" (" + activeTableAccount.alias + ")") - let footerText = "Files " + NSLocalizedString("_of_", comment: "") + " " + activeTableAccount.displayName + alias + let metadata = await NCManageDatabaseCreateMetadata().convertFileToMetadataAsync(file) - if error != .success { - completion(FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: activeTableAccount.userId, url: activeTableAccount.urlBase, account: activeTableAccount.account, tile: title, footerImage: "xmark.icloud", footerText: error.errorDescription)) - } else { - completion(FilesDataEntry(date: Date(), datas: datas, isPlaceholder: false, isEmpty: datas.isEmpty, userId: activeTableAccount.userId, url: activeTableAccount.urlBase, account: activeTableAccount.account, tile: title, footerImage: "checkmark.icloud", footerText: footerText)) - } + // DATA + let data = FilesData(id: metadata.ocId, image: image ?? UIImage(), title: metadata.fileNameView, subTitle: subTitle, url: url, useTypeIconFile: useTypeIconFile) + datas.append(data) + if datas.count == maxItems { + break } } + + let alias = (activeTableAccount.alias.isEmpty) ? "" : (" (" + activeTableAccount.alias + ")") + let footerText = "Files " + NSLocalizedString("_of_", comment: "") + " " + activeTableAccount.displayName + alias + + if results.error != .success { + return(FilesDataEntry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: activeTableAccount.userId, url: activeTableAccount.urlBase, account: activeTableAccount.account, tile: title, footerImage: "xmark.icloud", footerText: results.error.errorDescription)) + } else { + return(FilesDataEntry(date: Date(), datas: datas, isPlaceholder: false, isEmpty: datas.isEmpty, userId: activeTableAccount.userId, url: activeTableAccount.urlBase, account: activeTableAccount.account, tile: title, footerImage: "checkmark.icloud", footerText: footerText)) + } } diff --git a/Widget/Files/FilesWidgetProvider.swift b/Widget/Files/FilesWidgetProvider.swift index fdee4b6196..4ccce19047 100644 --- a/Widget/Files/FilesWidgetProvider.swift +++ b/Widget/Files/FilesWidgetProvider.swift @@ -12,20 +12,21 @@ struct FilesWidgetProvider: IntentTimelineProvider { typealias Intent = AccountIntent func placeholder(in context: Context) -> Entry { - let filesItems = getFilesItems(displaySize: context.displaySize) - let datasPlaceholder = Array(filesDatasTest[0...filesItems - 1]) + let datasPlaceholder = Array(filesDatasTest[0...4]) let title = getTitleFilesWidget(tableAccount: nil) return Entry(date: Date(), datas: datasPlaceholder, isPlaceholder: true, isEmpty: false, userId: "", url: "", account: "", tile: title, footerImage: "checkmark.icloud", footerText: NCBrandOptions.shared.brand + " files") } func getSnapshot(for configuration: AccountIntent, in context: Context, completion: @escaping (Entry) -> Void) { - getFilesDataEntry(configuration: configuration, isPreview: false, displaySize: context.displaySize) { entry in + Task { + let entry = await getFilesDataEntry(configuration: configuration, isPreview: false, displaySize: context.displaySize) completion(entry) } } func getTimeline(for configuration: AccountIntent, in context: Context, completion: @escaping (Timeline) -> Void) { - getFilesDataEntry(configuration: configuration, isPreview: context.isPreview, displaySize: context.displaySize) { entry in + Task { + let entry = await getFilesDataEntry(configuration: configuration, isPreview: context.isPreview, displaySize: context.displaySize) let timeLine = Timeline(entries: [entry], policy: .atEnd) completion(timeLine) } diff --git a/Widget/Files/FilesWidgetView.swift b/Widget/Files/FilesWidgetView.swift index 0dde0acc7c..305dbb4f83 100644 --- a/Widget/Files/FilesWidgetView.swift +++ b/Widget/Files/FilesWidgetView.swift @@ -49,39 +49,43 @@ struct FilesWidgetView: View { 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) + HStack(spacing: 10) { + Group { + if element.useTypeIconFile { + Image(uiImage: element.image) + .resizable() + .renderingMode(.template) + .foregroundColor(Color(NCBrandColor.shared.iconImageColor2)) + .frame(width: 32, height: 32) + } else { + Image(uiImage: element.image) + .resizable() + .scaledToFill() + .frame(width: 32, height: 32) + .clipped() + .cornerRadius(5) + } } VStack(alignment: .leading, spacing: 2) { Text(element.title) .font(.system(size: 12)) - .fontWeight(.regular) + .lineLimit(1) + .truncationMode(.tail) Text(element.subTitle) - .font(.system(size: CGFloat(10))) + .font(.system(size: 10)) .foregroundColor(Color(NCBrandColor.shared.iconImageColor2)) + .lineLimit(1) + .truncationMode(.tail) } - Spacer() + Spacer(minLength: 0) } - .padding(.leading, 10) - .frame(height: 50) + .padding(.horizontal, 10) + .frame(height: 44) } if element != entry.datas.last { Divider() - .padding(.leading, 54) + .padding(.leading, 52) } } } @@ -90,59 +94,63 @@ struct FilesWidgetView: View { .redacted(reason: entry.isPlaceholder ? .placeholder : []) } - HStack(spacing: 0) { - let sizeButton: CGFloat = 40 + HStack(spacing: 10) { + let buttonSize: CGFloat = 40 - Link(destination: entry.isPlaceholder ? linkNoAction : linkActionUploadAsset, label: { + Link(destination: entry.isPlaceholder ? linkNoAction : linkActionUploadAsset) { Image(systemName: "photo.badge.plus") .resizable() .renderingMode(.template) .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getText(account: entry.account))) + .frame(width: 18, height: 18) .padding(11) .background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getElement(account: entry.account))) .clipShape(Circle()) - .scaledToFit() - .frame(width: geo.size.width / 4, height: sizeButton) - }) + .frame(width: buttonSize, height: buttonSize) + } + .frame(maxWidth: .infinity) - Link(destination: entry.isPlaceholder ? linkNoAction : linkActionScanDocument, label: { + Link(destination: entry.isPlaceholder ? linkNoAction : linkActionScanDocument) { Image(systemName: "doc.text.viewfinder") .resizable() .renderingMode(.template) .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getText(account: entry.account))) + .frame(width: 18, height: 18) .padding(11) .background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getElement(account: entry.account))) .clipShape(Circle()) - .scaledToFit() - .font(Font.system(.body).weight(.light)) - .frame(width: geo.size.width / 4, height: sizeButton) - }) + .frame(width: buttonSize, height: buttonSize) + } + .frame(maxWidth: .infinity) - Link(destination: entry.isPlaceholder ? linkNoAction : linkActionTextDocument, label: { + Link(destination: entry.isPlaceholder ? linkNoAction : linkActionTextDocument) { Image("note.text") .resizable() .renderingMode(.template) .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getText(account: entry.account))) + .frame(width: 18, height: 18) .padding(11) .background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getElement(account: entry.account))) .clipShape(Circle()) - .scaledToFit() - .frame(width: geo.size.width / 4, height: sizeButton) - }) + .frame(width: buttonSize, height: buttonSize) + } + .frame(maxWidth: .infinity) - Link(destination: entry.isPlaceholder ? linkNoAction : linkActionVoiceMemo, label: { + Link(destination: entry.isPlaceholder ? linkNoAction : linkActionVoiceMemo) { Image("microphone") .resizable() .renderingMode(.template) .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getText(account: entry.account))) + .frame(width: 18, height: 18) .padding(11) .background(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getElement(account: entry.account))) .clipShape(Circle()) - .scaledToFit() - .frame(width: geo.size.width / 4, height: sizeButton) - }) + .frame(width: buttonSize, height: buttonSize) + } + .frame(maxWidth: .infinity) } - .frame(width: geo.size.width, height: geo.size.height - 22, alignment: .bottomTrailing) + .padding(.horizontal, 10) + .frame(maxWidth: .infinity, maxHeight: geo.size.height - 25, alignment: .bottom) .redacted(reason: entry.isPlaceholder ? .placeholder : []) HStack { @@ -158,8 +166,7 @@ struct FilesWidgetView: View { .lineLimit(1) .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getElement(account: entry.account))) } - .padding(.horizontal, 15.0) - .frame(maxWidth: geo.size.width, maxHeight: geo.size.height - 2, alignment: .bottomTrailing) + .frame(maxWidth: geo.size.width, maxHeight: geo.size.height, alignment: .bottomTrailing) } } .containerBackground(.background, for: .widget) @@ -169,7 +176,7 @@ struct FilesWidgetView: View { struct FilesWidget_Previews: PreviewProvider { static var previews: some View { let datas = Array(filesDatasTest[0...4]) - let entry = FilesDataEntry(date: Date(), datas: datas, isPlaceholder: false, isEmpty: true, userId: "", url: "", account: "", tile: "Good afternoon, Marino Faggiana", footerImage: "checkmark.icloud", footerText: "Nextcloud files") + let entry = FilesDataEntry(date: Date(), datas: datas, isPlaceholder: false, isEmpty: false, userId: "", url: "", account: "", tile: "Good afternoon, Marino Faggiana", footerImage: "checkmark.icloud", footerText: "Nextcloud files") FilesWidgetView(entry: entry).previewContext(WidgetPreviewContext(family: .systemLarge)) } } From b8798186e063e3ce59761755f60dee3bb2831139 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 17:58:57 +0100 Subject: [PATCH 19/20] code Signed-off-by: Marino Faggiana --- Widget/Files/FilesWidgetView.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Widget/Files/FilesWidgetView.swift b/Widget/Files/FilesWidgetView.swift index 305dbb4f83..029624fde3 100644 --- a/Widget/Files/FilesWidgetView.swift +++ b/Widget/Files/FilesWidgetView.swift @@ -56,14 +56,14 @@ struct FilesWidgetView: View { .resizable() .renderingMode(.template) .foregroundColor(Color(NCBrandColor.shared.iconImageColor2)) - .frame(width: 32, height: 32) + .scaledToFit() + .frame(width: 35, height: 35) } else { Image(uiImage: element.image) .resizable() - .scaledToFill() - .frame(width: 32, height: 32) - .clipped() - .cornerRadius(5) + .frame(width: 35, height: 35) + .background(Color(.secondarySystemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous)) } } @@ -101,6 +101,7 @@ struct FilesWidgetView: View { Image(systemName: "photo.badge.plus") .resizable() .renderingMode(.template) + .scaledToFit() .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getText(account: entry.account))) .frame(width: 18, height: 18) .padding(11) @@ -114,6 +115,7 @@ struct FilesWidgetView: View { Image(systemName: "doc.text.viewfinder") .resizable() .renderingMode(.template) + .scaledToFit() .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getText(account: entry.account))) .frame(width: 18, height: 18) .padding(11) @@ -127,6 +129,7 @@ struct FilesWidgetView: View { Image("note.text") .resizable() .renderingMode(.template) + .scaledToFit() .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getText(account: entry.account))) .frame(width: 18, height: 18) .padding(11) @@ -140,6 +143,7 @@ struct FilesWidgetView: View { Image("microphone") .resizable() .renderingMode(.template) + .scaledToFit() .foregroundColor(entry.isPlaceholder ? Color(.systemGray4) : Color(NCBrandColor.shared.getText(account: entry.account))) .frame(width: 18, height: 18) .padding(11) From a7595df653a2df60b4a2c460b4f5fac68765967b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 11 Feb 2026 18:21:34 +0100 Subject: [PATCH 20/20] code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 6 ++---- Widget/Dashboard/DashboardData.swift | 5 ++++- iOSClient/Utility/NCUtility+Image.swift | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 8217bccca4..02831e8684 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -570,7 +570,6 @@ F7802B322BD5584F00D74270 /* NCMedia+DragDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7802B312BD5584F00D74270 /* NCMedia+DragDrop.swift */; }; F7814E962F3B5F170074DA3A /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7814E952F3B5F170074DA3A /* NCSVGRenderer.swift */; }; F7814E972F3B5F170074DA3A /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7814E952F3B5F170074DA3A /* NCSVGRenderer.swift */; }; - F7814E982F3B5F580074DA3A /* NCSVGRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7814E952F3B5F170074DA3A /* NCSVGRenderer.swift */; }; F7816EF22C2C3E1F00A52517 /* NCPushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7816EF12C2C3E1F00A52517 /* NCPushNotification.swift */; }; F7817CF829801A3500FFBC65 /* Data+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7817CF729801A3500FFBC65 /* Data+Extension.swift */; }; F7817CFB29801A3500FFBC65 /* Data+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7817CF729801A3500FFBC65 /* Data+Extension.swift */; }; @@ -4335,7 +4334,6 @@ F724377C2C10B92200C7C68D /* NCSharePermissions.swift in Sources */, F77ED59328C9CEA000E24ED0 /* ToolbarWidgetProvider.swift in Sources */, F72A17D828B221E300F3F159 /* DashboardWidgetView.swift in Sources */, - F7814E982F3B5F580074DA3A /* NCSVGRenderer.swift in Sources */, F77ED59528C9CEA400E24ED0 /* ToolbarWidgetView.swift in Sources */, F78302FB28B4C3EE00B84583 /* NCManageDatabase+Video.swift in Sources */, F7C687EA2D22BDE5004757BC /* NCManageDatabase+RecommendedFiles.swift in Sources */, @@ -5751,7 +5749,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5817,7 +5815,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; diff --git a/Widget/Dashboard/DashboardData.swift b/Widget/Dashboard/DashboardData.swift index b37f4259f0..ccc9b5bf1e 100644 --- a/Widget/Dashboard/DashboardData.swift +++ b/Widget/Dashboard/DashboardData.swift @@ -168,9 +168,12 @@ func getDashboardDataEntry(configuration: DashboardIntent?, isPreview: Bool, dis let data = results.responseData?.data { if let image = UIImage(data: data) { iconImage = image - } else if let image = try? await NCSVGRenderer().renderSVGToUIImage(svgData: data) { + } + /* NO MEMORY + else if let image = try? await NCSVGRenderer().renderSVGToUIImage(svgData: data) { iconImage = image } + */ } } } diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index 1604b3b4f2..0f495107f4 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -262,6 +262,7 @@ extension NCUtility { return avatarImage } +#if !EXTENSION func convertSVGtoPNGWriteToUserData(serverUrl: String, size: CGFloat = 128, rewrite: Bool, @@ -331,6 +332,7 @@ extension NCUtility { } } } +#endif func getUserStatus(userIcon: String?, userStatus: String?, userMessage: String?) -> (statusImage: UIImage?, statusImageColor: UIColor, statusMessage: String, descriptionMessage: String) { var statusImage: UIImage?