diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index c1fe168859..02831e8684 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,8 @@ 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 */; }; 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 +600,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 +1518,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 +1845,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 +1864,6 @@ files = ( F3F0419D2B9F7E6E00D5155F /* RealmSwift in Frameworks */, F3391B082B4C52C5001C0C4B /* FirebaseDatabase in Frameworks */, - F3391B0C2B4C52D5001C0C4B /* SVGKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1885,7 +1881,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F711A4EF2AF932B900095DD8 /* SVGKitSwift in Frameworks */, F710FC80277B7D2700AA9FBF /* RealmSwift in Frameworks */, F74C863D2AEFBFD9009A1D4A /* LRUCache in Frameworks */, F70557B92ED44E4700135623 /* LucidBanner in Frameworks */, @@ -1906,7 +1901,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F787AC0B298BCB540001BB00 /* SVGKitSwift in Frameworks */, F783034428B5142B00B84583 /* NextcloudKit in Frameworks */, F33EE6E32BF4C00700CA1A51 /* NIOSSL in Frameworks */, F7160A7D2BE931DE0034DCB3 /* RealmSwift in Frameworks */, @@ -1941,7 +1935,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 +2932,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 +3420,6 @@ ); name = NextcloudUITests; packageProductDependencies = ( - F3391B0F2B4C52E6001C0C4B /* SVGKit */, F3391B152B4C52F6001C0C4B /* FirebaseDatabase */, F3F0419E2B9F7E7900D5155F /* RealmSwift */, F37208A32BAB63EE006B5430 /* QRCodeReader */, @@ -3469,7 +3462,6 @@ name = NextcloudIntegrationTests; packageProductDependencies = ( F3391B072B4C52C5001C0C4B /* FirebaseDatabase */, - F3391B0B2B4C52D5001C0C4B /* SVGKit */, F3F0419C2B9F7E6E00D5155F /* RealmSwift */, ); productName = NextcloudIntegrationTests; @@ -3524,7 +3516,6 @@ F7A560472AE15D5000BE8FD6 /* Queuer */, F760DE082AE66ED00027D78A /* KeychainAccess */, F74C863C2AEFBFD9009A1D4A /* LRUCache */, - F711A4EE2AF932B900095DD8 /* SVGKitSwift */, F33EE6E62BF4C02600CA1A51 /* NIOSSL */, F7B8F6172EAB7516006A70D6 /* JDStatusBarNotification */, F70557B82ED44E4700135623 /* LucidBanner */, @@ -3550,7 +3541,6 @@ packageProductDependencies = ( F783030C28B4C59A00B84583 /* SwiftEntryKit */, F783034328B5142B00B84583 /* NextcloudKit */, - F787AC0A298BCB540001BB00 /* SVGKitSwift */, F7A560452AE15D3D00BE8FD6 /* Queuer */, F760DE042AE66EBE0027D78A /* KeychainAccess */, F7160A7C2BE931DE0034DCB3 /* RealmSwift */, @@ -3622,7 +3612,6 @@ F77333872927A72100466E35 /* OpenSSL */, F77BC3EA293E5268005F2B08 /* Swifter */, F7D56B192972405500FA46C4 /* Mantis */, - F787AC08298BCB4A0001BB00 /* SVGKitSwift */, F7A1050D29E587AF00FFD92B /* TagListView */, F7F623B42A5EF4D30022D3D4 /* Gzip */, F76B649D2ADFFDEC00014640 /* LRUCache */, @@ -3794,7 +3783,6 @@ ); mainGroup = F7F67B9F1A24D27800EE80DA; packageReferences = ( - F75E57A725BF0D61002B72C2 /* XCRemoteSwiftPackageReference "SVGKit" */, F7ED547A25EEA65400956C55 /* XCRemoteSwiftPackageReference "QRCodeReader" */, F72DA9B225F53E4E00B87DB1 /* XCRemoteSwiftPackageReference "SwiftRichString" */, F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */, @@ -4255,6 +4243,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 */, @@ -4678,6 +4667,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 */, @@ -5759,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; @@ -5825,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; @@ -6054,14 +6044,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 +6205,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 +6385,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 +6540,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..ccc9b5bf1e 100644 --- a/Widget/Dashboard/DashboardData.swift +++ b/Widget/Dashboard/DashboardData.swift @@ -1,32 +1,12 @@ -// -// 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 import Intents import NextcloudKit import RealmSwift -import SVGKit struct DashboardDataEntry: TimelineEntry { let date: Date @@ -48,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(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")!, 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 { @@ -76,31 +55,7 @@ func getDashboardItems(displaySize: CGSize, withButton: Bool) -> Int { } } -func convertDataToImage(data: Data?, size: CGSize, fileNameToWrite: String?) -> 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 if let image = SVGKImage(data: data) { - image.size = size - imageData = image.uiImage - } else { - print("error") - } - 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) @@ -108,14 +63,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" @@ -126,22 +82,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, @@ -150,115 +98,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? + 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() - if let iconUrl = item.iconUrl, let url = URL(string: iconUrl) { - if let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) { + var imageAvatar: Bool = false + var imageColorized: Bool = false + var imageColor: UIColor? - let path = (urlComponents.path as NSString) - let pathComponents = path.components(separatedBy: "/") - let queryItems = urlComponents.queryItems + 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 - } - 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 - } + 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 } - // 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 = convertDataToImage(data: data, size: NCGlobal.shared.size256, fileNameToWrite: fileName) { - icon = image - } - } + /* NO MEMORY + 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, 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..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,18 +15,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..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) } } @@ -197,8 +148,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..12a31755bb 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 @@ -49,16 +30,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 { @@ -80,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" @@ -111,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, @@ -189,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? + let results = await NextcloudKit.shared.searchBodyRequestAsync(serverUrl: activeTableAccount.urlBase, requestBody: requestBody, showHiddenFiles: showHiddenFiles, account: activeTableAccount.account, options: options) - if file.directory || (!file.livePhotoFile.isEmpty && file.classFile == NKTypeClassFile.video.rawValue) { - continue - } + var datas: [FilesData] = [] + let title = getTitleFilesWidget(tableAccount: activeTableAccount) + let files = results.files?.sorted(by: { ($0.date as Date) > ($1.date as Date) }) ?? [] - // SUBTITLE - let subTitle = utility.getRelativeDateTitle(file.date as Date) + " · " + utilityFileSystem.transformedSize(file.size) + for file in files { + var useTypeIconFile = false + var image: UIImage? - // 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 } + if file.directory || (!file.livePhotoFile.isEmpty && file.classFile == NKTypeClassFile.video.rawValue) { + continue + } - // IMAGE + // 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 d49e360709..4ccce19047 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 @@ -31,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 1db7624e5d..029624fde3 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)) @@ -72,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)) + .scaledToFit() + .frame(width: 35, height: 35) + } else { + Image(uiImage: element.image) + .resizable() + .frame(width: 35, height: 35) + .background(Color(.secondarySystemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous)) + } } 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) } } } @@ -113,59 +94,67 @@ 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) + .scaledToFit() .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) + .scaledToFit() .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) + .scaledToFit() .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) + .scaledToFit() .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 { @@ -181,18 +170,17 @@ 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) } } - .widgetBackground(Color(UIColor.systemBackground)) + .containerBackground(.background, for: .widget) } } 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)) } } 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() diff --git a/iOSClient/Activity/NCActivity.swift b/iOSClient/Activity/NCActivity.swift index dd740a534f..9959cf54c6 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! @@ -274,31 +273,14 @@ 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) { - 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) - } - } 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 } - } + Task { + 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 627a11e8fa..67df203544 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,35 @@ 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(serverUrl: source, rewrite: false, account: account, id: idActivity) + if let image = results.image, + cell.fileId == results.id { 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(serverUrl: source, rewrite: false, account: account, id: idActivity) + if let image = results.image, + cell.fileId == results.id { 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 +166,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 +197,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 +211,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/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..c57af4a957 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`` @@ -503,24 +502,13 @@ 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) - let svgkImage = SVGKImage(data: data)?.uiImage.withRenderingMode(.alwaysTemplate) - iconImage = resizedRasterImage(svgkImage ?? UIImage(), to: .init(width: 23, height: 23)) - } else { - iconImage = UIImage() + var iconImage = UIImage() + if let iconUrl = item.icon { + if let image = await NCUtility().convertSVGtoPNGWriteToUserData(serverUrl: metadata.urlBase + iconUrl, + rewrite: false, + account: metadata.account).image { + iconImage = image + } } let action = await UIAction( @@ -528,13 +516,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 { diff --git a/iOSClient/Menu/NCContextMenuProfile.swift b/iOSClient/Menu/NCContextMenuProfile.swift index 91e4f9ecc1..79f77c65d7 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,35 @@ 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) + 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)") + } + } + } + + return uiAction } // MARK: - Action Handlers diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index 911e11b57d..7f5b772a63 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() @@ -123,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 { @@ -265,59 +298,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..84fc727b1e 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -340,7 +340,7 @@ 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 + if await self.utility.convertSVGtoPNGWriteToUserData(serverUrl: icon, rewrite: false, account: session.account).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 new file mode 100644 index 0000000000..be88278e0c --- /dev/null +++ b/iOSClient/Utility/NCSVGRenderer.swift @@ -0,0 +1,139 @@ +// 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? + private let utilityFileSystem = NCUtilityFileSystem() + + func renderSVGToUIImage(svgData: Data?, + size: CGSize = CGSize(width: 128, height: 128), + backgroundColor: UIColor = .clear) async throws -> UIImage? { + guard let svgData else { + return nil + } + + 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 + 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: logicalSize) + config.afterScreenUpdates = true + + let image = try await takeSnapshotAsync(webView: webView, configuration: config) + // 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 { + // 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) + } + } + + 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; + })(); + """ + + // 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: 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 { + 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 + } +} diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index c06468f8a2..0f495107f4 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 { @@ -128,11 +127,13 @@ 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) { - 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, @@ -261,79 +262,77 @@ 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) { - var fileNamePNG = "" - guard let svgUrlString = svgUrlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), - let iconURL = URL(string: svgUrlString) else { - return completion(nil, id) - } - if let fileName = fileName { - fileNamePNG = fileName - } else { - fileNamePNG = iconURL.deletingPathExtension().lastPathComponent + ".png" +#if !EXTENSION + func convertSVGtoPNGWriteToUserData(serverUrl: String, + size: CGFloat = 128, + rewrite: Bool, + account: String, + id: Int? = nil) async -> (image: UIImage?, id: Int?) { + var serverUrl = serverUrl + if let url = serverUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { + serverUrl = URL(string: url)?.absoluteString ?? serverUrl } - let imageNamePath = utilityFileSystem.createServerUrl(serverUrl: utilityFileSystem.directoryUserData, fileName: fileNamePNG) + 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: imageNamePath) || rewrite == true { - NextcloudKit.shared.downloadContent(serverUrl: iconURL.absoluteString, account: account) { task in + 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, - path: iconURL.absoluteString, + path: serverUrl, 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: pathPNG)) + 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 image, + let pngImageData = image.pngData() else { + return(nil, id) + } + try pngImageData.write(to: URL(fileURLWithPath: pathPNG)) + return(image, id) + } catch { + return(nil, id) + } } else { - return completion(imageNamePath, id) + do { + let data = try Data(contentsOf: URL(fileURLWithPath: pathPNG)) + return(UIImage(data: data), id) + } catch { + return(nil, id) + } } } +#endif func getUserStatus(userIcon: String?, userStatus: String?, userMessage: String?) -> (statusImage: UIImage?, statusImageColor: UIColor, statusMessage: String, descriptionMessage: String) { var statusImage: UIImage? diff --git a/iOSClient/Utility/NCUtilityFileSystem.swift b/iOSClient/Utility/NCUtilityFileSystem.swift index c00f6e8160..426089cc22 100644 --- a/iOSClient/Utility/NCUtilityFileSystem.swift +++ b/iOSClient/Utility/NCUtilityFileSystem.swift @@ -354,6 +354,20 @@ final class NCUtilityFileSystem: NSObject, @unchecked Sendable { } } + 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 2068227682..b06d71c602 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,8 @@ class NCViewerMedia: UIViewController { // MARK: - Image - func loadImage() { + @MainActor + func loadImage() async { guard let metadata = self.database.getMetadataFromOcId(metadata.ocId) else { return } self.metadata = metadata let fileNamePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, @@ -269,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 { @@ -299,23 +301,28 @@ 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) { + do { + 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) } - self.image = image - self.imageVideoContainer.image = self.image - return } + 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 @@ -363,7 +370,6 @@ class NCViewerMedia: UIViewController { self.allowOpeningDetails = false } taskHandler: { _ in } self.allowOpeningDetails = true - } } 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) }