зеркало из https://github.com/nextcloud/ios.git
V 5.2.0 (#2833)
Signed-off-by: Nextcloud bot <bot@nextcloud.com> Signed-off-by: Marino Faggiana <marino@marinofaggiana.com> Signed-off-by: Milen Pivchev <milen.pivchev@gmail.com> Signed-off-by: rakekniven <2069590+rakekniven@users.noreply.github.com> Co-authored-by: Nextcloud bot <bot@nextcloud.com> Co-authored-by: Milen Pivchev <milen.pivchev@gmail.com> Co-authored-by: rakekniven <2069590+rakekniven@users.noreply.github.com>
This commit is contained in:
Родитель
0f24e373b1
Коммит
62c79619fc
Двоичные данные
Animation.gif
Двоичные данные
Animation.gif
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 908 KiB После Ширина: | Высота: | Размер: 3.6 MiB |
|
@ -26,4 +26,4 @@ import Foundation
|
|||
// Database Realm
|
||||
//
|
||||
let databaseName = "nextcloud.realm"
|
||||
let databaseSchemaVersion: UInt64 = 342
|
||||
let databaseSchemaVersion: UInt64 = 345
|
||||
|
|
|
@ -217,7 +217,7 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
|
|||
NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
|
||||
for metadata in metadatasFolder {
|
||||
let serverUrl = metadata.serverUrl + "/" + metadata.fileNameView
|
||||
NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrl, account: metadata.account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: serverUrl, account: metadata.account)
|
||||
}
|
||||
let metadatas = NCManageDatabase.shared.getAdvancedMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", fileProviderData.shared.account, serverUrl), sorted: "fileName", ascending: true)
|
||||
completionHandler(metadatas)
|
||||
|
|
|
@ -48,7 +48,7 @@ extension FileProviderExtension {
|
|||
let isDirectoryEncrypted = self.utilityFileSystem.isDirectoryE2EE(file: file)
|
||||
let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryEncrypted)
|
||||
|
||||
NCManageDatabase.shared.addDirectory(encrypted: false, favorite: false, ocId: ocId!, fileId: metadata.fileId, etag: metadata.etag, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: false, favorite: false, ocId: ocId!, fileId: metadata.fileId, etag: metadata.etag, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
|
||||
NCManageDatabase.shared.addMetadata(metadata)
|
||||
|
||||
guard let metadataInsert = NCManageDatabase.shared.getMetadataFromOcId(ocId!) else {
|
||||
|
@ -194,7 +194,7 @@ extension FileProviderExtension {
|
|||
|
||||
if metadata.directory {
|
||||
|
||||
NCManageDatabase.shared.setDirectory(serverUrl: fileNamePathFrom, serverUrlTo: fileNamePathTo, etag: nil, ocId: nil, fileId: nil, encrypted: directoryTable.e2eEncrypted, richWorkspace: nil, account: account)
|
||||
NCManageDatabase.shared.setDirectory(serverUrl: fileNamePathFrom, serverUrlTo: fileNamePathTo, encrypted: directoryTable.e2eEncrypted, account: account)
|
||||
|
||||
} else {
|
||||
|
||||
|
|
|
@ -213,6 +213,7 @@
|
|||
F7239871253D86B600257F49 /* NCEmptyDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7239870253D86B600257F49 /* NCEmptyDataSet.swift */; };
|
||||
F7239877253D86D300257F49 /* NCEmptyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7239876253D86D300257F49 /* NCEmptyView.xib */; };
|
||||
F723B3DD22FC6D1D00301EFE /* NCShareCommentsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F723B3DC22FC6D1C00301EFE /* NCShareCommentsCell.xib */; };
|
||||
F72408332B8A27C900F128E2 /* NCMedia+Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = F72408322B8A27C900F128E2 /* NCMedia+Command.swift */; };
|
||||
F72429362AFE39860040AEF3 /* NCLivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70968A324212C4E00ED60E5 /* NCLivePhoto.swift */; };
|
||||
F72429372AFE39980040AEF3 /* NCLivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70968A324212C4E00ED60E5 /* NCLivePhoto.swift */; };
|
||||
F72429382AFE39A80040AEF3 /* NCLivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70968A324212C4E00ED60E5 /* NCLivePhoto.swift */; };
|
||||
|
@ -432,6 +433,7 @@
|
|||
F75379222AE2ADA100C0250E /* JGProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = F75379212AE2ADA100C0250E /* JGProgressHUD */; };
|
||||
F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */ = {isa = PBXBuildFile; productRef = F753BA92281FD8020015BFB6 /* EasyTipView */; };
|
||||
F755BD9B20594AC7008C5FBB /* NCService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F755BD9A20594AC7008C5FBB /* NCService.swift */; };
|
||||
F755CB402B8CB13C00CE27E9 /* NCMediaLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F755CB3F2B8CB13C00CE27E9 /* NCMediaLayout.swift */; };
|
||||
F757CC8229E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; };
|
||||
F757CC8329E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; };
|
||||
F757CC8429E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift in Sources */ = {isa = PBXBuildFile; fileRef = F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */; };
|
||||
|
@ -622,7 +624,6 @@
|
|||
F78ACD54219047D40088454D /* NCSectionFooter.xib in Resources */ = {isa = PBXBuildFile; fileRef = F78ACD53219047D40088454D /* NCSectionFooter.xib */; };
|
||||
F78B87E72B62527100C65ADC /* NCMediaDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */; };
|
||||
F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnaill.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnaill.swift */; };
|
||||
F78B87EB2B627AA100C65ADC /* NCMediaCommandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78B87EA2B627AA100C65ADC /* NCMediaCommandView.swift */; };
|
||||
F78C6FDE296D677300C952C3 /* NCContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78C6FDD296D677300C952C3 /* NCContextMenu.swift */; };
|
||||
F78E2D6529AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; };
|
||||
F78E2D6629AF02DB0024D4F3 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78E2D6429AF02DB0024D4F3 /* Database.swift */; };
|
||||
|
@ -712,7 +713,6 @@
|
|||
F7BB7E4727A18C56009B9F29 /* Parchment in Frameworks */ = {isa = PBXBuildFile; productRef = F7BB7E4627A18C56009B9F29 /* Parchment */; };
|
||||
F7BC287E26663F6C004D46C5 /* NCViewCertificateDetails.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7BC287D26663F6C004D46C5 /* NCViewCertificateDetails.storyboard */; };
|
||||
F7BC288026663F85004D46C5 /* NCViewCertificateDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BC287F26663F85004D46C5 /* NCViewCertificateDetails.swift */; };
|
||||
F7BD50312B65216300D5AEF9 /* NCMediaGridLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BD50302B65216300D5AEF9 /* NCMediaGridLayout.swift */; };
|
||||
F7BD71E62636EAFC00643C34 /* NCNetworkingE2EE.swift in Sources */ = {isa = PBXBuildFile; fileRef = F785EE9C246196DF00B3F945 /* NCNetworkingE2EE.swift */; };
|
||||
F7BF9D822934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */; };
|
||||
F7BF9D832934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */; };
|
||||
|
@ -781,7 +781,6 @@
|
|||
F7EE66AD2A20B226009AE765 /* UILabel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7EE66AC2A20B226009AE765 /* UILabel+Extension.swift */; };
|
||||
F7EFA47825ADBA500083159A /* NCViewerProviderContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7EFA47725ADBA500083159A /* NCViewerProviderContextMenu.swift */; };
|
||||
F7EFC0CD256BF8DD00461AAD /* NCUserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7EFC0CC256BF8DD00461AAD /* NCUserStatus.swift */; };
|
||||
F7F1E54C2492369A00E42386 /* NCMediaCommandView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */; };
|
||||
F7F4F10527ECDBDB008676F9 /* Inconsolata-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F7F4F0FD27ECDBDB008676F9 /* Inconsolata-SemiBold.ttf */; };
|
||||
F7F4F10627ECDBDB008676F9 /* Inconsolata-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F7F4F0FE27ECDBDB008676F9 /* Inconsolata-Medium.ttf */; };
|
||||
F7F4F10727ECDBDB008676F9 /* Inconsolata-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F7F4F0FF27ECDBDB008676F9 /* Inconsolata-Black.ttf */; };
|
||||
|
@ -1093,6 +1092,7 @@
|
|||
F7239870253D86B600257F49 /* NCEmptyDataSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCEmptyDataSet.swift; sourceTree = "<group>"; };
|
||||
F7239876253D86D300257F49 /* NCEmptyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCEmptyView.xib; sourceTree = "<group>"; };
|
||||
F723B3DC22FC6D1C00301EFE /* NCShareCommentsCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCShareCommentsCell.xib; sourceTree = "<group>"; };
|
||||
F72408322B8A27C900F128E2 /* NCMedia+Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMedia+Command.swift"; sourceTree = "<group>"; };
|
||||
F7245923289BB50B00474787 /* ThreadSafeDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafeDictionary.swift; sourceTree = "<group>"; };
|
||||
F7267A81225DFCE100D6DB7D /* AFNetworking.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AFNetworking.framework; path = Carthage/Build/iOS/AFNetworking.framework; sourceTree = "<group>"; };
|
||||
F72685E827C78E490019EF5E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
|
@ -1168,6 +1168,7 @@
|
|||
F753701922723E0D0041C76C /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F753701A22723EC80041C76C /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F755BD9A20594AC7008C5FBB /* NCService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCService.swift; sourceTree = "<group>"; };
|
||||
F755CB3F2B8CB13C00CE27E9 /* NCMediaLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCMediaLayout.swift; sourceTree = "<group>"; };
|
||||
F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Groupfolders.swift"; sourceTree = "<group>"; };
|
||||
F757CC8A29E82D0500F31428 /* NCGroupfolders.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCGroupfolders.storyboard; sourceTree = "<group>"; };
|
||||
F757CC8B29E82D0500F31428 /* NCGroupfolders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCGroupfolders.swift; sourceTree = "<group>"; };
|
||||
|
@ -1290,7 +1291,6 @@
|
|||
F78ACD53219047D40088454D /* NCSectionFooter.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCSectionFooter.xib; sourceTree = "<group>"; };
|
||||
F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDataSource.swift; sourceTree = "<group>"; };
|
||||
F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnaill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaDownloadThumbnaill.swift; sourceTree = "<group>"; };
|
||||
F78B87EA2B627AA100C65ADC /* NCMediaCommandView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaCommandView.swift; sourceTree = "<group>"; };
|
||||
F78C6FDD296D677300C952C3 /* NCContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCContextMenu.swift; sourceTree = "<group>"; };
|
||||
F78D6F461F0B7CB9002F9619 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
F78D6F4D1F0B7CE4002F9619 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
|
@ -1390,7 +1390,6 @@
|
|||
F7BB04851FD58ACB00BBFD2A /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
F7BC287D26663F6C004D46C5 /* NCViewCertificateDetails.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NCViewCertificateDetails.storyboard; sourceTree = "<group>"; };
|
||||
F7BC287F26663F85004D46C5 /* NCViewCertificateDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewCertificateDetails.swift; sourceTree = "<group>"; };
|
||||
F7BD50302B65216300D5AEF9 /* NCMediaGridLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaGridLayout.swift; sourceTree = "<group>"; };
|
||||
F7BE7C25290AC8C9002ABB61 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intent.strings; sourceTree = "<group>"; };
|
||||
F7BE7C27290ADEFD002ABB61 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Intent.strings; sourceTree = "<group>"; };
|
||||
F7BE7C29290ADEFD002ABB61 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Intent.strings; sourceTree = "<group>"; };
|
||||
|
@ -1493,7 +1492,6 @@
|
|||
F7EE66AC2A20B226009AE765 /* UILabel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Extension.swift"; sourceTree = "<group>"; };
|
||||
F7EFA47725ADBA500083159A /* NCViewerProviderContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerProviderContextMenu.swift; sourceTree = "<group>"; };
|
||||
F7EFC0CC256BF8DD00461AAD /* NCUserStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCUserStatus.swift; sourceTree = "<group>"; };
|
||||
F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCMediaCommandView.xib; sourceTree = "<group>"; };
|
||||
F7F35B592578FB63003F5589 /* CollaboraOnlineWebViewKeyboardManager.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CollaboraOnlineWebViewKeyboardManager.framework; path = Carthage/Build/iOS/CollaboraOnlineWebViewKeyboardManager.framework; sourceTree = "<group>"; };
|
||||
F7F4F0FD27ECDBDB008676F9 /* Inconsolata-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inconsolata-SemiBold.ttf"; sourceTree = "<group>"; };
|
||||
F7F4F0FE27ECDBDB008676F9 /* Inconsolata-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inconsolata-Medium.ttf"; sourceTree = "<group>"; };
|
||||
|
@ -2538,11 +2536,10 @@
|
|||
F720B5B72507B9A5008C94E5 /* Cell */,
|
||||
F7501C302212E57400FB1415 /* NCMedia.storyboard */,
|
||||
F7501C312212E57400FB1415 /* NCMedia.swift */,
|
||||
F78B87EA2B627AA100C65ADC /* NCMediaCommandView.swift */,
|
||||
F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */,
|
||||
F72408322B8A27C900F128E2 /* NCMedia+Command.swift */,
|
||||
F78B87E62B62527100C65ADC /* NCMediaDataSource.swift */,
|
||||
F78B87E82B62550800C65ADC /* NCMediaDownloadThumbnaill.swift */,
|
||||
F7BD50302B65216300D5AEF9 /* NCMediaGridLayout.swift */,
|
||||
F755CB3F2B8CB13C00CE27E9 /* NCMediaLayout.swift */,
|
||||
F741C2232B6B9FD600E849BB /* NCMediaSelectTabBar.swift */,
|
||||
);
|
||||
path = Media;
|
||||
|
@ -3372,7 +3369,6 @@
|
|||
F7F4F10B27ECDBDB008676F9 /* Inconsolata-Light.ttf in Resources */,
|
||||
3704EB2A23D5A58400455C5B /* NCMenu.storyboard in Resources */,
|
||||
AF93471C27E2361E002537EE /* NCShareAdvancePermissionHeader.xib in Resources */,
|
||||
F7F1E54C2492369A00E42386 /* NCMediaCommandView.xib in Resources */,
|
||||
F76032A0252F0F8E0015A421 /* NCTransferCell.xib in Resources */,
|
||||
F7F4F10527ECDBDB008676F9 /* Inconsolata-SemiBold.ttf in Resources */,
|
||||
F7A48415297028FC00BD1B49 /* Nextcloud Hub.png in Resources */,
|
||||
|
@ -3927,8 +3923,9 @@
|
|||
F76B649C2ADFFAED00014640 /* NCImageCache.swift in Sources */,
|
||||
F78A18B823CDE2B300F681F3 /* NCViewerRichWorkspace.swift in Sources */,
|
||||
F7A60F86292D215000FCE1F2 /* NCShareAccounts.swift in Sources */,
|
||||
F72408332B8A27C900F128E2 /* NCMedia+Command.swift in Sources */,
|
||||
F77910AB25DD53C700CEDB9E /* NCSettingsBundleHelper.swift in Sources */,
|
||||
F78B87EB2B627AA100C65ADC /* NCMediaCommandView.swift in Sources */,
|
||||
F755CB402B8CB13C00CE27E9 /* NCMediaLayout.swift in Sources */,
|
||||
F73EF7B72B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */,
|
||||
AF4BF61927562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */,
|
||||
F73EF7E72B0226B90087E6E9 /* NCManageDatabase+UserStatus.swift in Sources */,
|
||||
|
@ -4001,7 +3998,6 @@
|
|||
F72944F22A84246400246839 /* NCEndToEndMetadataV20.swift in Sources */,
|
||||
F70BFC7420E0FA7D00C67599 /* NCUtility.swift in Sources */,
|
||||
F79EDAA526B004980007D134 /* NCPlayer.swift in Sources */,
|
||||
F7BD50312B65216300D5AEF9 /* NCMediaGridLayout.swift in Sources */,
|
||||
F7C1EEA525053A9C00866ACC /* NCDataSource.swift in Sources */,
|
||||
F713FF002472764100214AF6 /* UIImage+animatedGIF.m in Sources */,
|
||||
AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */,
|
||||
|
@ -4995,7 +4991,7 @@
|
|||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 9;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = NKUJUXUJ3B;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
@ -5021,7 +5017,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.1.0;
|
||||
MARKETING_VERSION = 5.2.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -5060,7 +5056,7 @@
|
|||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 9;
|
||||
DEVELOPMENT_TEAM = NKUJUXUJ3B;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
|
@ -5083,7 +5079,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.1.0;
|
||||
MARKETING_VERSION = 5.2.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -5375,7 +5371,7 @@
|
|||
repositoryURL = "https://github.com/nextcloud/NextcloudKit";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 2.9.6;
|
||||
version = 2.9.7;
|
||||
};
|
||||
};
|
||||
F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = {
|
||||
|
|
|
@ -241,7 +241,8 @@ func getFilesDataEntry(configuration: AccountIntent?, isPreview: Bool, displaySi
|
|||
let fileNamePathOrFileId = utilityFileSystem.getFileNamePath(file.fileName, serverUrl: file.serverUrl, urlBase: file.urlBase, userId: file.userId)
|
||||
let fileNamePreviewLocalPath = utilityFileSystem.getDirectoryProviderStoragePreviewOcId(file.ocId, etag: file.etag)
|
||||
let fileNameIconLocalPath = utilityFileSystem.getDirectoryProviderStorageIconOcId(file.ocId, etag: file.etag)
|
||||
let (_, _, imageIcon, _, _, _) = await NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId, fileNamePreviewLocalPath: fileNamePreviewLocalPath, widthPreview: NCGlobal.shared.sizePreview, heightPreview: NCGlobal.shared.sizePreview, fileNameIconLocalPath: fileNameIconLocalPath, sizeIcon: NCGlobal.shared.sizeIcon, options: options)
|
||||
let sizePreview = NCUtility().getSizePreview(width: Int(file.width), height: Int(file.height))
|
||||
let (_, _, imageIcon, _, _, _) = await NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePathOrFileId, fileNamePreviewLocalPath: fileNamePreviewLocalPath, widthPreview: Int(sizePreview.width), heightPreview: Int(sizePreview.height), fileNameIconLocalPath: fileNameIconLocalPath, sizeIcon: NCGlobal.shared.sizeIcon, options: options)
|
||||
if let image = imageIcon {
|
||||
imageRecent = image
|
||||
}
|
||||
|
|
|
@ -141,15 +141,6 @@ extension NCAccountRequest: UITableViewDelegate {
|
|||
progressView.progress = 0
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if decelerate {
|
||||
// startTimer()
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
// startTimer()
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
return heightCell
|
||||
|
|
|
@ -124,6 +124,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
NCManageDatabase.shared.setCapabilities(account: account)
|
||||
|
||||
NCBrandColor.shared.settingThemingColor(account: activeAccount.account)
|
||||
DispatchQueue.global().async {
|
||||
NCImageCache.shared.createMediaCache(account: self.account, withCacheSize: true)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -181,8 +184,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application did become active")
|
||||
|
||||
DispatchQueue.global().async { NCImageCache.shared.createMediaCache(account: self.account) }
|
||||
|
||||
NCSettingsBundleHelper.setVersionAndBuildNumber()
|
||||
NCSettingsBundleHelper.checkAndExecuteSettings(delay: 0.5)
|
||||
|
||||
|
@ -245,10 +246,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
|
||||
// L' applicazione è entrata nello sfondo
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Application did enter in background")
|
||||
|
||||
guard !account.isEmpty else { return }
|
||||
|
||||
let activeAccount = NCManageDatabase.shared.getActiveAccount()
|
||||
|
||||
if let autoUpload = activeAccount?.autoUpload, autoUpload {
|
||||
|
@ -599,6 +599,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
|
||||
guard let tableAccount = NCManageDatabase.shared.setAccountActive(account) else { return }
|
||||
|
||||
if account != self.account {
|
||||
DispatchQueue.global().async {
|
||||
if NCManageDatabase.shared.getAccounts()?.count == 1 {
|
||||
NCImageCache.shared.createMediaCache(account: account, withCacheSize: true)
|
||||
} else {
|
||||
NCImageCache.shared.createMediaCache(account: account, withCacheSize: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.account = tableAccount.account
|
||||
self.urlBase = tableAccount.urlBase
|
||||
self.user = tableAccount.user
|
||||
|
@ -620,10 +630,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Initialize Auto upload with \(items) uploads")
|
||||
}
|
||||
|
||||
DispatchQueue.global().async {
|
||||
NCImageCache.shared.createMediaCache(account: self.account)
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeUser)
|
||||
}
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeUser)
|
||||
}
|
||||
|
||||
@objc func deleteAccount(_ account: String, wipe: Bool) {
|
||||
|
|
|
@ -47,28 +47,29 @@ class tableDirectory: Object {
|
|||
|
||||
extension NCManageDatabase {
|
||||
|
||||
func addDirectory(encrypted: Bool, favorite: Bool, ocId: String, fileId: String, etag: String? = nil, permissions: String? = nil, serverUrl: String, account: String) {
|
||||
func addDirectory(e2eEncrypted: Bool, favorite: Bool, ocId: String, fileId: String, etag: String? = nil, permissions: String? = nil, richWorkspace: String? = nil, serverUrl: String, account: String) {
|
||||
do {
|
||||
let realm = try Realm()
|
||||
try realm.write {
|
||||
var addObject = tableDirectory()
|
||||
if let result = realm.objects(tableDirectory.self).filter("ocId == %@", ocId).first {
|
||||
addObject = result
|
||||
if let result = realm.objects(tableDirectory.self).filter("account == %@ AND ocId == %@", account, ocId).first {
|
||||
result.e2eEncrypted = e2eEncrypted
|
||||
result.favorite = favorite
|
||||
if let etag { result.etag = etag }
|
||||
if let permissions { result.permissions = permissions }
|
||||
if let richWorkspace { result.richWorkspace = richWorkspace }
|
||||
} else {
|
||||
addObject.ocId = ocId
|
||||
let result = tableDirectory()
|
||||
result.e2eEncrypted = e2eEncrypted
|
||||
result.favorite = favorite
|
||||
result.ocId = ocId
|
||||
result.fileId = fileId
|
||||
if let etag { result.etag = etag }
|
||||
if let permissions { result.permissions = permissions }
|
||||
if let richWorkspace { result.richWorkspace = richWorkspace }
|
||||
result.serverUrl = serverUrl
|
||||
result.account = account
|
||||
realm.add(result, update: .all)
|
||||
}
|
||||
addObject.account = account
|
||||
addObject.e2eEncrypted = encrypted
|
||||
addObject.favorite = favorite
|
||||
addObject.fileId = fileId
|
||||
if let etag = etag {
|
||||
addObject.etag = etag
|
||||
}
|
||||
if let permissions = permissions {
|
||||
addObject.permissions = permissions
|
||||
}
|
||||
addObject.serverUrl = serverUrl
|
||||
realm.add(addObject, update: .all)
|
||||
}
|
||||
} catch let error {
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
|
||||
|
|
|
@ -34,8 +34,8 @@ class tableE2eEncryptionLock: Object {
|
|||
@Persisted var e2eToken = ""
|
||||
}
|
||||
|
||||
typealias tableE2eEncryption = tableE2eEncryptionV3
|
||||
class tableE2eEncryptionV3: Object {
|
||||
typealias tableE2eEncryption = tableE2eEncryptionV4
|
||||
class tableE2eEncryptionV4: Object {
|
||||
|
||||
@Persisted(primaryKey: true) var primaryKey = ""
|
||||
@Persisted var account = ""
|
||||
|
@ -47,7 +47,7 @@ class tableE2eEncryptionV3: Object {
|
|||
@Persisted var initializationVector = ""
|
||||
@Persisted var metadataKey = ""
|
||||
@Persisted var metadataKeyIndex: Int = 0
|
||||
@Persisted var metadataVersion: Double = 0
|
||||
@Persisted var version: String = ""
|
||||
@Persisted var mimeType = ""
|
||||
@Persisted var ocIdServerUrl: String = ""
|
||||
@Persisted var serverUrl = ""
|
||||
|
|
|
@ -121,6 +121,7 @@ class tableMetadata: Object, NCUserBaseUrl {
|
|||
@objc dynamic var userId = ""
|
||||
@objc dynamic var latitude: Double = 0
|
||||
@objc dynamic var longitude: Double = 0
|
||||
@objc dynamic var altitude: Double = 0
|
||||
@objc dynamic var height: Int = 0
|
||||
@objc dynamic var width: Int = 0
|
||||
@objc dynamic var errorCode: Int = 0
|
||||
|
@ -272,6 +273,14 @@ extension tableMetadata {
|
|||
!isFlaggedAsLivePhotoByServer
|
||||
}
|
||||
|
||||
var imageSize: CGSize {
|
||||
CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
var hasPreviewBorder: Bool {
|
||||
!isImage && !isAudioOrVideo && hasPreview && NCUtilityFileSystem().fileProviderStoragePreviewIconExists(ocId, etag: etag)
|
||||
}
|
||||
|
||||
/// Returns false if the user is lokced out of the file. I.e. The file is locked but by somone else
|
||||
func canUnlock(as user: String) -> Bool {
|
||||
return !lock || (lockOwner == user && lockOwnerType == 0)
|
||||
|
@ -360,8 +369,9 @@ extension NCManageDatabase {
|
|||
metadata.userId = file.userId
|
||||
metadata.latitude = file.latitude
|
||||
metadata.longitude = file.longitude
|
||||
metadata.height = file.height
|
||||
metadata.width = file.width
|
||||
metadata.altitude = file.altitude
|
||||
metadata.height = Int(file.height)
|
||||
metadata.width = Int(file.width)
|
||||
metadata.livePhotoFile = file.livePhotoFile
|
||||
metadata.isFlaggedAsLivePhotoByServer = file.isFlaggedAsLivePhotoByServer
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class NCFiles: NCCollectionViewCommon {
|
|||
}
|
||||
|
||||
self.titleCurrentFolder = self.getNavigationTitle()
|
||||
self.setNavigationItems()
|
||||
self.setNavigationLeftItems()
|
||||
|
||||
self.reloadDataSource()
|
||||
self.reloadDataSourceNetwork()
|
||||
|
@ -204,11 +204,25 @@ class NCFiles: NCCollectionViewCommon {
|
|||
NCKeychain().isEndToEndEnabled(account: self.appDelegate.account),
|
||||
!NCNetworkingE2EE().isInUpload(account: self.appDelegate.account, serverUrl: self.serverUrl) {
|
||||
let lock = NCManageDatabase.shared.getE2ETokenLock(account: self.appDelegate.account, serverUrl: self.serverUrl)
|
||||
NextcloudKit.shared.getE2EEMetadata(fileId: metadataFolder.ocId, e2eToken: lock?.e2eToken, options: NCNetworkingE2EE().getOptions()) { account, e2eMetadata, signature, _, error in
|
||||
NCNetworkingE2EE().getMetadata(fileId: metadataFolder.ocId, e2eToken: lock?.e2eToken) { account, version, e2eMetadata, signature, _, error in
|
||||
if error == .success, let e2eMetadata = e2eMetadata {
|
||||
let error = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: signature, serverUrl: self.serverUrl, account: account, urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
|
||||
if error == .success {
|
||||
self.reloadDataSource()
|
||||
if version == "v1", NCGlobal.shared.capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 {
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("[E2EE] Conversion v1 to v2")
|
||||
NCActivityIndicator.shared.start()
|
||||
Task {
|
||||
let serverUrl = metadataFolder.serverUrl + "/" + metadataFolder.fileName
|
||||
let error = await NCNetworkingE2EE().uploadMetadata(account: metadataFolder.account, serverUrl: serverUrl, userId: metadataFolder.userId, updateVersionV1V2: true)
|
||||
if error != .success {
|
||||
NCContentPresenter().showError(error: error)
|
||||
}
|
||||
NCActivityIndicator.shared.stop()
|
||||
self.reloadDataSource()
|
||||
}
|
||||
} else {
|
||||
self.reloadDataSource()
|
||||
}
|
||||
} else {
|
||||
// Client Diagnostic
|
||||
NCManageDatabase.shared.addDiagnostic(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)
|
||||
|
|
|
@ -93,7 +93,7 @@ class NCGroupfolders: NCCollectionViewCommon {
|
|||
let isDirectoryE2EE = self.utilityFileSystem.isDirectoryE2EE(file: file)
|
||||
let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE)
|
||||
NCManageDatabase.shared.addMetadata(metadata)
|
||||
NCManageDatabase.shared.addDirectory(encrypted: isDirectoryE2EE, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: isDirectoryE2EE, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
var listLayout: NCListLayout!
|
||||
var gridLayout: NCGridLayout!
|
||||
var literalSearch: String?
|
||||
var isReloadDataSourceNetworkInProgress: Bool = false
|
||||
var isReloadDataSourceNetworkInProgress: Bool = false {
|
||||
didSet {
|
||||
DispatchQueue.main.async {
|
||||
self.setNavigationRightItems(enableMoreMenu: !self.isReloadDataSourceNetworkInProgress)
|
||||
}
|
||||
}
|
||||
}
|
||||
var tabBarSelect: NCSelectableViewTabBar?
|
||||
|
||||
var timerNotificationCenter: Timer?
|
||||
|
@ -82,6 +88,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
var emptyTitle: String = ""
|
||||
var emptyDescription: String = ""
|
||||
|
||||
private var showDescription: Bool {
|
||||
!headerRichWorkspaceDisable && NCKeychain().showDescription
|
||||
}
|
||||
|
||||
private var infoLabelsSeparator: String {
|
||||
layoutForView?.layout == NCGlobal.shared.layoutList ? " - " : ""
|
||||
}
|
||||
|
||||
// MARK: - View Life Cycle
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
@ -215,12 +229,14 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
navigationController?.navigationBar.prefersLargeTitles = true
|
||||
navigationController?.setNavigationBarHidden(false, animated: true)
|
||||
navigationController?.setNavigationBarAppearance()
|
||||
setNavigationItems()
|
||||
setNavigationLeftItems()
|
||||
|
||||
// FIXME: iPAD PDF landscape mode iOS 16
|
||||
DispatchQueue.main.async {
|
||||
self.collectionView?.collectionViewLayout.invalidateLayout()
|
||||
}
|
||||
|
||||
setNavigationRightItems(enableMoreMenu: false)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
|
@ -261,8 +277,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
// TIP
|
||||
self.tipView?.dismiss()
|
||||
|
||||
isEditMode = false
|
||||
setNavigationItems()
|
||||
toggleSelect(isOn: false)
|
||||
}
|
||||
|
||||
func presentationControllerDidDismiss( _ presentationController: UIPresentationController) {
|
||||
|
@ -321,7 +336,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
let error = userInfo["error"] as? NKError,
|
||||
error.errorCode != NCGlobal.shared.errorNotModified else { return }
|
||||
|
||||
setNavigationItems()
|
||||
setNavigationLeftItems()
|
||||
}
|
||||
|
||||
@objc func changeTheming() {
|
||||
|
@ -560,18 +575,22 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
cell.writeInfoDateSize(date: metadata.date, size: metadata.size)
|
||||
} else {
|
||||
cell.fileInfoLabel?.text = ""
|
||||
cell.fileSubinfoLabel?.text = ""
|
||||
}
|
||||
} else {
|
||||
cell.fileProgressView?.isHidden = false
|
||||
cell.fileProgressView?.progress = progressNumber.floatValue
|
||||
cell.setButtonMore(named: NCGlobal.shared.buttonMoreStop, image: NCImageCache.images.buttonStop)
|
||||
if status == NCGlobal.shared.metadataStatusDownloading {
|
||||
cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + " - ↓ " + self.utilityFileSystem.transformedSize(totalBytes)
|
||||
cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected)
|
||||
cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↓ " + self.utilityFileSystem.transformedSize(totalBytes)
|
||||
} else if status == NCGlobal.shared.metadataStatusUploading {
|
||||
if totalBytes > 0 {
|
||||
cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + " - ↑ " + self.utilityFileSystem.transformedSize(totalBytes)
|
||||
cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected)
|
||||
cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ " + self.utilityFileSystem.transformedSize(totalBytes)
|
||||
} else {
|
||||
cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected) + " - ↑ …"
|
||||
cell.fileInfoLabel?.text = self.utilityFileSystem.transformedSize(totalBytesExpected)
|
||||
cell.fileSubinfoLabel?.text = self.infoLabelsSeparator + "↑ …"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -591,9 +610,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
|
||||
// MARK: - Layout
|
||||
|
||||
func setNavigationItems() {
|
||||
|
||||
self.setNavigationRightItems()
|
||||
func setNavigationLeftItems() {
|
||||
navigationItem.title = titleCurrentFolder
|
||||
|
||||
guard layoutKey == NCGlobal.shared.layoutViewFiles else { return }
|
||||
|
@ -602,40 +619,53 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
|
||||
let activeAccount = NCManageDatabase.shared.getActiveAccount()
|
||||
|
||||
let image = utility.loadUserImage(
|
||||
for: appDelegate.user,
|
||||
displayName: activeAccount?.displayName,
|
||||
userBaseUrl: appDelegate)
|
||||
let image = utility.loadUserImage(for: appDelegate.user, displayName: activeAccount?.displayName, userBaseUrl: appDelegate)
|
||||
|
||||
let button = UIButton(type: .custom)
|
||||
let button = AccountSwitcherButton(type: .custom)
|
||||
button.setImage(image, for: .normal)
|
||||
|
||||
button.setImage(image, for: .highlighted)
|
||||
button.semanticContentAttribute = .forceLeftToRight
|
||||
button.sizeToFit()
|
||||
button.action(for: .touchUpInside) { _ in
|
||||
|
||||
let accounts = NCManageDatabase.shared.getAllAccountOrderAlias()
|
||||
if !accounts.isEmpty, !NCBrandOptions.shared.disable_multiaccount, !NCBrandOptions.shared.disable_manage_account {
|
||||
let accounts = NCManageDatabase.shared.getAllAccountOrderAlias()
|
||||
|
||||
if let vcAccountRequest = UIStoryboard(name: "NCAccountRequest", bundle: nil).instantiateInitialViewController() as? NCAccountRequest {
|
||||
if !accounts.isEmpty, !NCBrandOptions.shared.disable_multiaccount, !NCBrandOptions.shared.disable_manage_account {
|
||||
let accountActions: [UIAction] = accounts.map { account in
|
||||
let image = utility.loadUserImage(for: account.user, displayName: account.displayName, userBaseUrl: account)
|
||||
|
||||
vcAccountRequest.activeAccount = NCManageDatabase.shared.getActiveAccount()
|
||||
vcAccountRequest.accounts = accounts
|
||||
vcAccountRequest.enableTimerProgress = false
|
||||
vcAccountRequest.enableAddAccount = true
|
||||
vcAccountRequest.delegate = self
|
||||
vcAccountRequest.dismissDidEnterBackground = true
|
||||
var name: String = ""
|
||||
var url: String = ""
|
||||
|
||||
let screenHeighMax = UIScreen.main.bounds.height - (UIScreen.main.bounds.height / 5)
|
||||
let numberCell = accounts.count + 1
|
||||
let height = min(CGFloat(numberCell * Int(vcAccountRequest.heightCell) + 45), screenHeighMax)
|
||||
|
||||
let popup = NCPopupViewController(contentController: vcAccountRequest, popupWidth: 300, popupHeight: height)
|
||||
|
||||
self.present(popup, animated: true)
|
||||
if account.alias.isEmpty {
|
||||
name = account.displayName
|
||||
url = (URL(string: account.urlBase)?.host ?? "")
|
||||
} else {
|
||||
name = account.alias
|
||||
}
|
||||
|
||||
// TIP
|
||||
let action = UIAction(title: name, image: image, state: account.active ? .on : .off) { _ in
|
||||
if !account.active {
|
||||
self.appDelegate.changeAccount(account.account, userProfile: nil)
|
||||
}
|
||||
}
|
||||
|
||||
action.subtitle = url
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
let addAccountAction = UIAction(title: NSLocalizedString("_add_account_", comment: ""), image: .init(systemName: "person.crop.circle.badge.plus")) { _ in
|
||||
self.appDelegate.openLogin(viewController: self, selector: NCGlobal.shared.introLogin, openLoginWeb: false)
|
||||
}
|
||||
|
||||
let addAccountSubmenu = UIMenu(title: "", options: .displayInline, children: [addAccountAction])
|
||||
|
||||
let menu = UIMenu(children: accountActions + [addAccountSubmenu])
|
||||
|
||||
button.menu = menu
|
||||
button.showsMenuAsPrimaryAction = true
|
||||
|
||||
button.onMenuOpened = {
|
||||
self.dismissTip()
|
||||
}
|
||||
}
|
||||
|
@ -889,7 +919,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
|
|||
}
|
||||
|
||||
@objc func reloadDataSourceNetwork() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
DispatchQueue.main.async {
|
||||
self.isReloadDataSourceNetworkInProgress = true
|
||||
self.collectionView?.reloadData()
|
||||
}
|
||||
|
@ -1117,49 +1147,63 @@ extension NCCollectionViewCommon: UICollectionViewDelegate {
|
|||
extension NCCollectionViewCommon: UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||
guard let metadata = dataSource.cellForItemAt(indexPath: indexPath) else { return }
|
||||
guard let metadata = dataSource.cellForItemAt(indexPath: indexPath),
|
||||
let cell = (cell as? NCCellProtocol) else { return }
|
||||
|
||||
cell.filePreviewImageView?.layer.borderWidth = 0
|
||||
|
||||
if metadata.isImage {
|
||||
cell.filePreviewImageView?.contentMode = .scaleAspectFill
|
||||
} else {
|
||||
cell.filePreviewImageView?.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
// Thumbnail
|
||||
if !metadata.directory {
|
||||
if metadata.hasPreviewBorder {
|
||||
cell.filePreviewImageView?.layer.borderWidth = 0.2
|
||||
cell.filePreviewImageView?.layer.borderColor = UIColor.systemGray3.cgColor
|
||||
}
|
||||
if metadata.name == NCGlobal.shared.appName {
|
||||
if let image = utility.createFilePreviewImage(ocId: metadata.ocId, etag: metadata.etag, fileNameView: metadata.fileNameView, classFile: metadata.classFile, status: metadata.status, createPreviewMedia: !metadata.hasPreview) {
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = image
|
||||
if let image = utility.createFilePreviewImage(ocId: metadata.ocId, etag: metadata.etag, fileNameView: metadata.fileNameView, classFile: metadata.classFile, status: metadata.status, createPreviewMedia: !metadata.hasPreview) {
|
||||
|
||||
cell.filePreviewImageView?.image = image
|
||||
} else {
|
||||
if metadata.iconName.isEmpty {
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.file
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.file
|
||||
} else {
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = UIImage(named: metadata.iconName)
|
||||
cell.filePreviewImageView?.image = UIImage(named: metadata.iconName)
|
||||
}
|
||||
if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
|
||||
for case let operation as NCCollectionViewDownloadThumbnail in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId { return }
|
||||
NCNetworking.shared.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: (cell as? NCCellProtocol), collectionView: collectionView))
|
||||
NCNetworking.shared.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, cell: cell, collectionView: collectionView))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unified search
|
||||
switch metadata.iconName {
|
||||
case let str where str.contains("contacts"):
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconContacts
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.iconContacts
|
||||
case let str where str.contains("conversation"):
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconTalk
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.iconTalk
|
||||
case let str where str.contains("calendar"):
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconCalendar
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.iconCalendar
|
||||
case let str where str.contains("deck"):
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconDeck
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.iconDeck
|
||||
case let str where str.contains("mail"):
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconMail
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.iconMail
|
||||
case let str where str.contains("talk"):
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconTalk
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.iconTalk
|
||||
case let str where str.contains("confirm"):
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconConfirm
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.iconConfirm
|
||||
case let str where str.contains("pages"):
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.iconPages
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.iconPages
|
||||
default:
|
||||
(cell as? NCCellProtocol)?.filePreviewImageView?.image = NCImageCache.images.file
|
||||
cell.filePreviewImageView?.image = NCImageCache.images.file
|
||||
}
|
||||
|
||||
if !metadata.iconUrl.isEmpty {
|
||||
if let ownerId = getAvatarFromIconUrl(metadata: metadata), let cell = cell as? NCCellProtocol {
|
||||
if let ownerId = getAvatarFromIconUrl(metadata: metadata) {
|
||||
let fileName = metadata.userBaseUrl + "-" + ownerId + ".png"
|
||||
NCNetworking.shared.downloadAvatar(user: ownerId, dispalyName: nil, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.filePreviewImageView)
|
||||
}
|
||||
|
@ -1170,8 +1214,7 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
|
|||
// Avatar
|
||||
if !metadata.ownerId.isEmpty,
|
||||
metadata.ownerId != appDelegate.userId,
|
||||
appDelegate.account == metadata.account,
|
||||
let cell = cell as? NCCellProtocol {
|
||||
appDelegate.account == metadata.account {
|
||||
let fileName = metadata.userBaseUrl + "-" + metadata.ownerId + ".png"
|
||||
NCNetworking.shared.downloadAvatar(user: metadata.ownerId, dispalyName: metadata.ownerDisplayName, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.fileAvatarImageView)
|
||||
}
|
||||
|
@ -1195,8 +1238,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
|
|||
let numberItems = dataSource.numberOfItemsInSection(section)
|
||||
emptyDataSet?.numberOfItemsInSection(numberItems, section: section)
|
||||
|
||||
setNavigationRightItems()
|
||||
|
||||
return numberItems
|
||||
}
|
||||
|
||||
|
@ -1258,7 +1299,9 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
|
|||
} else {
|
||||
cell.fileInfoLabel?.text = metadata.subline
|
||||
}
|
||||
cell.fileSubinfoLabel?.isHidden = true
|
||||
} else {
|
||||
cell.fileSubinfoLabel?.isHidden = false
|
||||
cell.fileTitleLabel?.text = metadata.fileNameView
|
||||
cell.fileTitleLabel?.lineBreakMode = .byTruncatingMiddle
|
||||
cell.writeInfoDateSize(date: metadata.date, size: metadata.size)
|
||||
|
@ -1351,14 +1394,18 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
|
|||
// Write status on Label Info
|
||||
switch metadata.status {
|
||||
case NCGlobal.shared.metadataStatusWaitDownload:
|
||||
cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_wait_download_", comment: "")
|
||||
cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size)
|
||||
cell.fileSubinfoLabel?.text = infoLabelsSeparator + NSLocalizedString("_status_wait_download_", comment: "")
|
||||
case NCGlobal.shared.metadataStatusDownloading:
|
||||
cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - ↓ …"
|
||||
cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size)
|
||||
cell.fileSubinfoLabel?.text = infoLabelsSeparator + "↓ …"
|
||||
case NCGlobal.shared.metadataStatusWaitUpload:
|
||||
cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - " + NSLocalizedString("_status_wait_upload_", comment: "")
|
||||
cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size)
|
||||
cell.fileSubinfoLabel?.text = infoLabelsSeparator + NSLocalizedString("_status_wait_upload_", comment: "")
|
||||
cell.fileLocalImage?.image = nil
|
||||
case NCGlobal.shared.metadataStatusUploading:
|
||||
cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size) + " - ↑ …"
|
||||
cell.fileInfoLabel?.text = utilityFileSystem.transformedSize(metadata.size)
|
||||
cell.fileSubinfoLabel?.text = infoLabelsSeparator + "↑ …"
|
||||
cell.fileLocalImage?.image = nil
|
||||
case NCGlobal.shared.metadataStatusUploadError:
|
||||
if metadata.sessionError.isEmpty {
|
||||
|
@ -1407,9 +1454,13 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
|
|||
}
|
||||
|
||||
// Accessibility
|
||||
cell.setAccessibility(label: metadata.fileNameView + ", " + (cell.fileInfoLabel?.text ?? ""), value: a11yValues.joined(separator: ", "))
|
||||
cell.setAccessibility(label: metadata.fileNameView + ", " + (cell.fileInfoLabel?.text ?? "") + (cell.fileSubinfoLabel?.text ?? ""), value: a11yValues.joined(separator: ", "))
|
||||
|
||||
// Color string find in search
|
||||
|
||||
cell.fileTitleLabel?.textColor = .label
|
||||
cell.fileTitleLabel?.font = .systemFont(ofSize: 15)
|
||||
|
||||
if isSearchingMode, let literalSearch = self.literalSearch, let title = cell.fileTitleLabel?.text {
|
||||
let longestWordRange = (title.lowercased() as NSString).range(of: literalSearch)
|
||||
let attributedString = NSMutableAttributedString(string: title, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)])
|
||||
|
@ -1547,7 +1598,7 @@ extension NCCollectionViewCommon: UICollectionViewDelegateFlowLayout {
|
|||
|
||||
var headerRichWorkspace: CGFloat = 0
|
||||
|
||||
if let richWorkspaceText = richWorkspaceText, !headerRichWorkspaceDisable {
|
||||
if let richWorkspaceText = richWorkspaceText, showDescription {
|
||||
let trimmed = richWorkspaceText.trimmingCharacters(in: .whitespaces)
|
||||
if !trimmed.isEmpty && !isSearchingMode {
|
||||
headerRichWorkspace = UIScreen.main.bounds.size.height / 6
|
||||
|
@ -1610,7 +1661,7 @@ extension NCCollectionViewCommon: EasyTipViewDelegate {
|
|||
}
|
||||
|
||||
extension NCCollectionViewCommon: NCSelectableNavigationView, NCCollectionViewCommonSelectTabBarDelegate {
|
||||
func setNavigationRightItems() {
|
||||
func setNavigationRightItems(enableMoreMenu: Bool = true) {
|
||||
var selectedMetadatas: [tableMetadata] = []
|
||||
var isAnyOffline = false
|
||||
var isAnyDirectory = false
|
||||
|
@ -1641,6 +1692,7 @@ extension NCCollectionViewCommon: NCSelectableNavigationView, NCCollectionViewCo
|
|||
}
|
||||
|
||||
guard !isAnyOffline else { continue }
|
||||
|
||||
if metadata.directory,
|
||||
let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", appDelegate.account, metadata.serverUrl + "/" + metadata.fileName)) {
|
||||
isAnyOffline = directory.offline
|
||||
|
@ -1675,6 +1727,8 @@ extension NCCollectionViewCommon: NCSelectableNavigationView, NCCollectionViewCo
|
|||
let menu = UIMenu(children: createMenuActions())
|
||||
let menuButton = UIBarButtonItem(image: .init(systemName: "ellipsis.circle"), menu: menu)
|
||||
|
||||
menuButton.isEnabled = enableMoreMenu
|
||||
|
||||
if layoutKey == NCGlobal.shared.layoutViewFiles {
|
||||
navigationItem.rightBarButtonItems = [menuButton, notification]
|
||||
} else {
|
||||
|
@ -1866,12 +1920,22 @@ extension NCCollectionViewCommon: NCSelectableNavigationView, NCCollectionViewCo
|
|||
self.saveLayout(layoutForView)
|
||||
}
|
||||
|
||||
let foldersSubmenu = UIMenu(title: "", options: .displayInline, children: [foldersOnTop])
|
||||
let showDescriptionKeychain = NCKeychain().showDescription
|
||||
|
||||
let showDescription = UIAction(title: NSLocalizedString("_show_description_", comment: ""), image: UIImage(systemName: "list.dash.header.rectangle"), attributes: richWorkspaceText == nil ? .disabled : [], state: showDescriptionKeychain && richWorkspaceText != nil ? .on : .off) { _ in
|
||||
NCKeychain().showDescription = !showDescriptionKeychain
|
||||
self.collectionView.reloadData()
|
||||
self.setNavigationRightItems()
|
||||
}
|
||||
|
||||
showDescription.subtitle = richWorkspaceText == nil ? NSLocalizedString("_no_description_available_", comment: "") : ""
|
||||
|
||||
let additionalSubmenu = UIMenu(title: "", options: .displayInline, children: [foldersOnTop, showDescription])
|
||||
|
||||
if layoutKey == NCGlobal.shared.layoutViewRecent {
|
||||
return [select]
|
||||
} else {
|
||||
return [select, viewStyleSubmenu, sortSubmenu, foldersSubmenu]
|
||||
return [select, viewStyleSubmenu, sortSubmenu, additionalSubmenu]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1918,7 +1982,7 @@ extension NCCollectionViewCommon {
|
|||
return
|
||||
}
|
||||
|
||||
// DOWNLOAD
|
||||
// DOWNLOAD FOREGROUND
|
||||
if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload {
|
||||
if let request = NCNetworking.shared.downloadRequest[fileNameLocalPath] {
|
||||
request.cancel()
|
||||
|
@ -1937,6 +2001,18 @@ extension NCCollectionViewCommon {
|
|||
return
|
||||
}
|
||||
|
||||
// DOWNLOAD BACKGROUND
|
||||
if metadata.session == NCNetworking.shared.sessionDownloadBackground {
|
||||
let session: URLSession? = NCNetworking.shared.sessionManagerDownloadBackground
|
||||
if let tasks = await session?.tasks {
|
||||
for task in tasks.2 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
|
||||
if task.taskIdentifier == metadata.sessionTaskIdentifier {
|
||||
task.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UPLOAD FOREGROUND
|
||||
if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierUpload {
|
||||
if let request = NCNetworking.shared.uploadRequest[fileNameLocalPath] {
|
||||
|
@ -2030,18 +2106,19 @@ class NCCollectionViewDownloadThumbnail: ConcurrentOperation {
|
|||
}
|
||||
|
||||
override func start() {
|
||||
|
||||
guard !isCancelled else { return self.finish() }
|
||||
|
||||
var etagResource: String?
|
||||
let sizePreview = NCUtility().getSizePreview(width: metadata.width, height: metadata.height)
|
||||
|
||||
if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) {
|
||||
etagResource = metadata.etagResource
|
||||
}
|
||||
|
||||
NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePath,
|
||||
fileNamePreviewLocalPath: fileNamePreviewLocalPath,
|
||||
widthPreview: NCGlobal.shared.sizePreview,
|
||||
heightPreview: NCGlobal.shared.sizePreview,
|
||||
widthPreview: Int(sizePreview.width),
|
||||
heightPreview: Int(sizePreview.height),
|
||||
fileNameIconLocalPath: fileNameIconLocalPath,
|
||||
sizeIcon: NCGlobal.shared.sizeIcon,
|
||||
etag: etagResource,
|
||||
|
@ -2051,6 +2128,10 @@ class NCCollectionViewDownloadThumbnail: ConcurrentOperation {
|
|||
NCManageDatabase.shared.setMetadataEtagResource(ocId: self.metadata.ocId, etagResource: etag)
|
||||
DispatchQueue.main.async {
|
||||
if self.metadata.ocId == self.cell?.fileObjectId, let filePreviewImageView = self.cell?.filePreviewImageView {
|
||||
if self.metadata.hasPreviewBorder {
|
||||
self.cell?.filePreviewImageView?.layer.borderWidth = 0.2
|
||||
self.cell?.filePreviewImageView?.layer.borderColor = UIColor.systemGray3.cgColor
|
||||
}
|
||||
UIView.transition(with: filePreviewImageView,
|
||||
duration: 0.75,
|
||||
options: .transitionCrossDissolve,
|
||||
|
@ -2065,3 +2146,12 @@ class NCCollectionViewDownloadThumbnail: ConcurrentOperation {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AccountSwitcherButton: UIButton {
|
||||
var onMenuOpened: (() -> Void)?
|
||||
|
||||
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willDisplayMenuFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
|
||||
super.contextMenuInteraction(interaction, willDisplayMenuFor: configuration, animator: animator)
|
||||
onMenuOpened?()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,9 +32,9 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
@IBOutlet weak var imageLocal: UIImageView!
|
||||
@IBOutlet weak var labelTitle: UILabel!
|
||||
@IBOutlet weak var labelInfo: UILabel!
|
||||
@IBOutlet weak var labelSubinfo: UILabel!
|
||||
@IBOutlet weak var buttonMore: UIButton!
|
||||
@IBOutlet weak var imageVisualEffect: UIVisualEffectView!
|
||||
@IBOutlet weak var progressView: UIProgressView!
|
||||
|
||||
var objectId = ""
|
||||
var indexPath = IndexPath()
|
||||
|
@ -63,9 +63,9 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
get { return labelInfo }
|
||||
set { labelInfo = newValue }
|
||||
}
|
||||
var fileProgressView: UIProgressView? {
|
||||
get { return progressView }
|
||||
set { progressView = newValue }
|
||||
var fileSubinfoLabel: UILabel? {
|
||||
get { return labelSubinfo }
|
||||
set { labelSubinfo = newValue }
|
||||
}
|
||||
var fileSelectImage: UIImageView? {
|
||||
get { return imageSelect }
|
||||
|
@ -100,10 +100,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
imageVisualEffect.clipsToBounds = true
|
||||
imageVisualEffect.alpha = 0.5
|
||||
|
||||
progressView.tintColor = NCBrandColor.shared.brand
|
||||
progressView.transform = CGAffineTransform(scaleX: 1.0, y: 0.5)
|
||||
progressView.trackTintColor = .clear
|
||||
|
||||
let longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(gestureRecognizer:)))
|
||||
longPressedGesture.minimumPressDuration = 0.5
|
||||
longPressedGesture.delegate = self
|
||||
|
@ -118,8 +114,6 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
|
||||
labelTitle.text = ""
|
||||
labelInfo.text = ""
|
||||
labelTitle.textColor = .label
|
||||
labelInfo.textColor = .systemGray
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
|
@ -209,7 +203,8 @@ class NCGridCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
dateFormatter.timeStyle = .none
|
||||
dateFormatter.locale = Locale.current
|
||||
|
||||
labelInfo.text = dateFormatter.string(from: date as Date) + " · " + NCUtilityFileSystem().transformedSize(size)
|
||||
labelInfo.text = dateFormatter.string(from: date as Date)
|
||||
labelSubinfo.text = NCUtilityFileSystem().transformedSize(size)
|
||||
}
|
||||
|
||||
func setAccessibility(label: String, value: String) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
@ -12,110 +12,123 @@
|
|||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="gridCell" id="vf1-Kf-9uL" customClass="NCGridCell" customModule="Nextcloud" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="416" height="494"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="416" height="529"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="416" height="494"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="416" height="529"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="5Ci-V1-hf5" userLabel="imageItem">
|
||||
<rect key="frame" x="0.0" y="0.0" width="416" height="434"/>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="5Ci-V1-hf5" userLabel="imageItem">
|
||||
<rect key="frame" x="0.0" y="20" width="416" height="419"/>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eU3-lY-fKr" userLabel="labelTitle">
|
||||
<rect key="frame" x="5" y="444" width="406" height="13.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="11"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2po-8g-XeS">
|
||||
<rect key="frame" x="5" y="464.5" width="386" height="12"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="10"/>
|
||||
<color key="textColor" systemColor="systemGray2Color"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EJs-Ro-nbe" userLabel="buttonMoreGrid">
|
||||
<rect key="frame" x="391" y="458" width="25" height="25"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="top" translatesAutoresizingMaskIntoConstraints="NO" id="VRH-IZ-lXO">
|
||||
<rect key="frame" x="5" y="447" width="406" height="36"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="254" horizontalCompressionResistancePriority="756" verticalCompressionResistancePriority="759" text="Label" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eU3-lY-fKr" userLabel="labelTitle">
|
||||
<rect key="frame" x="0.0" y="0.0" width="406" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="25" id="4Ba-Uy-pX2"/>
|
||||
<constraint firstAttribute="width" constant="25" id="aRK-GA-Nba"/>
|
||||
<constraint firstAttribute="height" constant="36" id="Soj-7j-hoM"/>
|
||||
</constraints>
|
||||
<state key="normal" image="more"/>
|
||||
<connections>
|
||||
<action selector="touchUpInsideMore:" destination="vf1-Kf-9uL" eventType="touchUpInside" id="GDx-NN-gE9"/>
|
||||
</connections>
|
||||
</button>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="AYs-f2-vve" userLabel="imageFavorite">
|
||||
<rect key="frame" x="396" y="5" width="15" height="15"/>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2po-8g-XeS">
|
||||
<rect key="frame" x="25" y="490" width="366" height="12"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="10"/>
|
||||
<color key="textColor" systemColor="systemGrayColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="12P-pO-DHO" userLabel="Label Size">
|
||||
<rect key="frame" x="25" y="505" width="366" height="12"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="10"/>
|
||||
<color key="textColor" systemColor="systemGrayColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="AYs-f2-vve" userLabel="imageFavorite">
|
||||
<rect key="frame" x="391" y="25" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="15" id="ZjS-Hv-JNm"/>
|
||||
<constraint firstAttribute="width" constant="15" id="kDr-15-VeJ"/>
|
||||
<constraint firstAttribute="height" constant="20" id="ZjS-Hv-JNm"/>
|
||||
<constraint firstAttribute="width" constant="20" id="kDr-15-VeJ"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="a0p-rj-jnV" userLabel="imageStatus">
|
||||
<rect key="frame" x="5" y="414" width="15" height="15"/>
|
||||
<rect key="frame" x="5" y="414" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="15" id="gq1-0a-eLC"/>
|
||||
<constraint firstAttribute="width" constant="15" id="uJE-4b-Qt7"/>
|
||||
<constraint firstAttribute="height" constant="20" id="gq1-0a-eLC"/>
|
||||
<constraint firstAttribute="width" constant="20" id="uJE-4b-Qt7"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="81G-wH-fjN" userLabel="imageLocal">
|
||||
<rect key="frame" x="396" y="414" width="15" height="15"/>
|
||||
<rect key="frame" x="391" y="414" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="15" id="NTa-Gi-uzY"/>
|
||||
<constraint firstAttribute="width" constant="15" id="xLe-lb-N1p"/>
|
||||
<constraint firstAttribute="height" constant="20" id="NTa-Gi-uzY"/>
|
||||
<constraint firstAttribute="width" constant="20" id="xLe-lb-N1p"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<visualEffectView hidden="YES" opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="W0L-HY-al1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="416" height="434"/>
|
||||
<rect key="frame" x="0.0" y="20" width="416" height="419"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="0m6-A2-SwD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="416" height="434"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="416" height="419"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<blurEffect style="extraLight"/>
|
||||
</visualEffectView>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="DHy-Up-3Bh" userLabel="imageSelect">
|
||||
<rect key="frame" x="5" y="5" width="25" height="25"/>
|
||||
<rect key="frame" x="5" y="25" width="25" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="25" id="SoZ-J3-98x"/>
|
||||
<constraint firstAttribute="width" constant="25" id="cZG-gx-gwt"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<progressView hidden="YES" opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JQo-Vc-Ejk">
|
||||
<rect key="frame" x="5" y="485" width="406" height="4"/>
|
||||
</progressView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EJs-Ro-nbe" userLabel="buttonMoreGrid">
|
||||
<rect key="frame" x="396" y="492" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="4Ba-Uy-pX2"/>
|
||||
<constraint firstAttribute="width" constant="20" id="aRK-GA-Nba"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="10"/>
|
||||
<color key="tintColor" systemColor="systemGray2Color"/>
|
||||
<state key="normal" image="ellipsis" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="touchUpInsideMore:" destination="vf1-Kf-9uL" eventType="touchUpInside" id="GDx-NN-gE9"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</view>
|
||||
<viewLayoutGuide key="safeArea" id="VXh-sQ-LeX"/>
|
||||
<constraints>
|
||||
<constraint firstItem="DHy-Up-3Bh" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" constant="5" id="1T3-8p-uIW"/>
|
||||
<constraint firstItem="AYs-f2-vve" firstAttribute="leading" secondItem="5Ci-V1-hf5" secondAttribute="trailing" constant="-20" id="3e3-0A-NSl"/>
|
||||
<constraint firstItem="eU3-lY-fKr" firstAttribute="top" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="10" id="4Yq-Nh-z1l"/>
|
||||
<constraint firstItem="2po-8g-XeS" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" constant="5" id="5dp-1s-MQi"/>
|
||||
<constraint firstItem="2po-8g-XeS" firstAttribute="top" secondItem="eU3-lY-fKr" secondAttribute="bottom" constant="7" id="5wo-Td-XeT"/>
|
||||
<constraint firstItem="AYs-f2-vve" firstAttribute="leading" secondItem="5Ci-V1-hf5" secondAttribute="trailing" constant="-25" id="3e3-0A-NSl"/>
|
||||
<constraint firstItem="W0L-HY-al1" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" id="6tC-PK-fYX"/>
|
||||
<constraint firstItem="EJs-Ro-nbe" firstAttribute="centerY" secondItem="2po-8g-XeS" secondAttribute="centerY" id="8qW-SF-u1h"/>
|
||||
<constraint firstItem="EJs-Ro-nbe" firstAttribute="leading" secondItem="2po-8g-XeS" secondAttribute="trailing" id="ABr-PB-TZg"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="trailing" secondItem="JQo-Vc-Ejk" secondAttribute="trailing" constant="5" id="E03-Dk-iZ5"/>
|
||||
<constraint firstItem="2po-8g-XeS" firstAttribute="leading" secondItem="vf1-Kf-9uL" secondAttribute="leading" constant="25" id="9JI-KS-d7J"/>
|
||||
<constraint firstItem="DHy-Up-3Bh" firstAttribute="top" secondItem="VXh-sQ-LeX" secondAttribute="top" constant="5" id="ESV-qE-tbO"/>
|
||||
<constraint firstAttribute="trailing" secondItem="VRH-IZ-lXO" secondAttribute="trailing" constant="5" id="HAS-uF-AuS"/>
|
||||
<constraint firstItem="2po-8g-XeS" firstAttribute="top" secondItem="VRH-IZ-lXO" secondAttribute="bottom" constant="7" id="OPl-xq-XOx"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="top" secondItem="VXh-sQ-LeX" secondAttribute="top" id="Ouj-ZD-UFm"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="trailing" secondItem="EJs-Ro-nbe" secondAttribute="trailing" id="Pfe-J0-t9I"/>
|
||||
<constraint firstItem="W0L-HY-al1" firstAttribute="top" secondItem="VXh-sQ-LeX" secondAttribute="top" id="Py6-0z-K3t"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="leading" secondItem="a0p-rj-jnV" secondAttribute="trailing" constant="-20" id="UtQ-6D-cYc"/>
|
||||
<constraint firstItem="2po-8g-XeS" firstAttribute="centerX" secondItem="vf1-Kf-9uL" secondAttribute="centerX" id="TTW-HT-yEO"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="leading" secondItem="a0p-rj-jnV" secondAttribute="trailing" constant="-25" id="UtQ-6D-cYc"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="trailing" secondItem="W0L-HY-al1" secondAttribute="trailing" id="VMW-0Y-aOH"/>
|
||||
<constraint firstItem="81G-wH-fjN" firstAttribute="top" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="-20" id="aEb-vq-8sk"/>
|
||||
<constraint firstItem="81G-wH-fjN" firstAttribute="top" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="-25" id="aEb-vq-8sk"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="trailing" secondItem="5Ci-V1-hf5" secondAttribute="trailing" id="cHT-cP-NN6"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="trailing" secondItem="eU3-lY-fKr" secondAttribute="trailing" constant="5" id="csl-Ny-rdF"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="bottom" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="60" id="eEC-eB-alE"/>
|
||||
<constraint firstItem="eU3-lY-fKr" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" constant="5" id="gZe-FC-8XQ"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="bottom" secondItem="W0L-HY-al1" secondAttribute="bottom" constant="60" id="jI9-M1-Nl8"/>
|
||||
<constraint firstItem="81G-wH-fjN" firstAttribute="leading" secondItem="5Ci-V1-hf5" secondAttribute="trailing" constant="-20" id="nFH-Pc-end"/>
|
||||
<constraint firstItem="VRH-IZ-lXO" firstAttribute="top" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="8" id="cj5-Ez-3cy"/>
|
||||
<constraint firstItem="VRH-IZ-lXO" firstAttribute="leading" secondItem="vf1-Kf-9uL" secondAttribute="leading" constant="5" id="dTF-dl-Awr"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="bottom" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="90" id="eEC-eB-alE"/>
|
||||
<constraint firstItem="12P-pO-DHO" firstAttribute="leading" secondItem="vf1-Kf-9uL" secondAttribute="leading" constant="25" id="fTQ-Ye-Xj2"/>
|
||||
<constraint firstItem="12P-pO-DHO" firstAttribute="top" secondItem="2po-8g-XeS" secondAttribute="bottom" constant="3" id="hbc-KL-fRj"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="bottom" secondItem="W0L-HY-al1" secondAttribute="bottom" constant="90" id="jI9-M1-Nl8"/>
|
||||
<constraint firstItem="EJs-Ro-nbe" firstAttribute="trailing" secondItem="5Ci-V1-hf5" secondAttribute="trailing" id="l6Z-DK-OZi"/>
|
||||
<constraint firstItem="81G-wH-fjN" firstAttribute="leading" secondItem="5Ci-V1-hf5" secondAttribute="trailing" constant="-25" id="nFH-Pc-end"/>
|
||||
<constraint firstItem="EJs-Ro-nbe" firstAttribute="top" secondItem="VRH-IZ-lXO" secondAttribute="bottom" constant="9" id="o5n-Oi-Uh7"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" id="qT3-WD-iTV"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="top" secondItem="AYs-f2-vve" secondAttribute="bottom" constant="-20" id="rLL-6g-ypv"/>
|
||||
<constraint firstItem="a0p-rj-jnV" firstAttribute="top" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="-20" id="upV-Ov-WWd"/>
|
||||
<constraint firstItem="JQo-Vc-Ejk" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" constant="5" id="wiV-1m-wt8"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="bottom" secondItem="JQo-Vc-Ejk" secondAttribute="bottom" constant="5" id="zV9-iQ-Zm5"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="top" secondItem="AYs-f2-vve" secondAttribute="bottom" constant="-25" id="rLL-6g-ypv"/>
|
||||
<constraint firstItem="a0p-rj-jnV" firstAttribute="top" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="-25" id="upV-Ov-WWd"/>
|
||||
<constraint firstItem="12P-pO-DHO" firstAttribute="centerX" secondItem="vf1-Kf-9uL" secondAttribute="centerX" id="xhm-Np-2Ua"/>
|
||||
</constraints>
|
||||
<size key="customSize" width="416" height="489"/>
|
||||
<size key="customSize" width="416" height="524"/>
|
||||
<connections>
|
||||
<outlet property="buttonMore" destination="EJs-Ro-nbe" id="BdI-ay-LuX"/>
|
||||
<outlet property="imageFavorite" destination="AYs-f2-vve" id="UeH-R7-bZr"/>
|
||||
|
@ -125,16 +138,19 @@
|
|||
<outlet property="imageStatus" destination="a0p-rj-jnV" id="6Dg-tf-evd"/>
|
||||
<outlet property="imageVisualEffect" destination="W0L-HY-al1" id="WDW-2d-Npa"/>
|
||||
<outlet property="labelInfo" destination="2po-8g-XeS" id="FJ4-wI-9cW"/>
|
||||
<outlet property="labelSubinfo" destination="12P-pO-DHO" id="K6d-7r-FGh"/>
|
||||
<outlet property="labelTitle" destination="eU3-lY-fKr" id="0P7-yM-Asb"/>
|
||||
<outlet property="progressView" destination="JQo-Vc-Ejk" id="cdf-7W-tao"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="244.80000000000001" y="244.6776611694153"/>
|
||||
<point key="canvasLocation" x="233.59999999999999" y="242.42878560719643"/>
|
||||
</collectionViewCell>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="more" width="425" height="425"/>
|
||||
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
||||
<systemColor name="systemGray2Color">
|
||||
<color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemGrayColor">
|
||||
<color red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
@ -32,6 +32,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
@IBOutlet weak var imageLocal: UIImageView!
|
||||
@IBOutlet weak var labelTitle: UILabel!
|
||||
@IBOutlet weak var labelInfo: UILabel!
|
||||
@IBOutlet weak var labelSubinfo: UILabel!
|
||||
@IBOutlet weak var imageShared: UIImageView!
|
||||
@IBOutlet weak var buttonShared: UIButton!
|
||||
@IBOutlet weak var imageMore: UIImageView!
|
||||
|
@ -44,7 +45,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
@IBOutlet weak var imageItemLeftConstraint: NSLayoutConstraint!
|
||||
@IBOutlet weak var separatorHeightConstraint: NSLayoutConstraint!
|
||||
@IBOutlet weak var titleTrailingConstraint: NSLayoutConstraint!
|
||||
@IBOutlet weak var infoTrailingConstraint: NSLayoutConstraint!
|
||||
@IBOutlet weak var subInfoTrailingConstraint: NSLayoutConstraint!
|
||||
|
||||
private var objectId = ""
|
||||
private var user = ""
|
||||
|
@ -76,6 +77,10 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
get { return labelInfo }
|
||||
set { labelInfo = newValue }
|
||||
}
|
||||
var fileSubinfoLabel: UILabel? {
|
||||
get { return labelSubinfo }
|
||||
set { labelSubinfo = newValue }
|
||||
}
|
||||
var fileProgressView: UIProgressView? {
|
||||
get { return progressView }
|
||||
set { progressView = newValue }
|
||||
|
@ -144,6 +149,7 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
labelInfo.text = ""
|
||||
labelTitle.textColor = .label
|
||||
labelInfo.textColor = .systemGray
|
||||
labelSubinfo.textColor = .systemGray
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
|
@ -190,12 +196,12 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
|
||||
func titleInfoTrailingFull() {
|
||||
titleTrailingConstraint.constant = 10
|
||||
infoTrailingConstraint.constant = 10
|
||||
subInfoTrailingConstraint.constant = 10
|
||||
}
|
||||
|
||||
func titleInfoTrailingDefault() {
|
||||
titleTrailingConstraint.constant = 90
|
||||
infoTrailingConstraint.constant = 90
|
||||
subInfoTrailingConstraint.constant = 90
|
||||
}
|
||||
|
||||
func setButtonMore(named: String, image: UIImage) {
|
||||
|
@ -271,7 +277,8 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
}
|
||||
|
||||
func writeInfoDateSize(date: NSDate, size: Int64) {
|
||||
labelInfo.text = NCUtility().dateDiff(date as Date) + " · " + NCUtilityFileSystem().transformedSize(size)
|
||||
labelInfo.text = NCUtility().dateDiff(date as Date)
|
||||
labelSubinfo.text = " · " + NCUtilityFileSystem().transformedSize(size)
|
||||
}
|
||||
|
||||
func setAccessibility(label: String, value: String) {
|
||||
|
@ -284,10 +291,13 @@ class NCListCell: UICollectionViewCell, UIGestureRecognizerDelegate, NCCellProto
|
|||
tag0.isHidden = true
|
||||
tag1.isHidden = true
|
||||
labelInfo.isHidden = false
|
||||
labelSubinfo.isHidden = false
|
||||
} else {
|
||||
tag0.isHidden = false
|
||||
tag1.isHidden = true
|
||||
labelInfo.isHidden = true
|
||||
labelSubinfo.isHidden = true
|
||||
|
||||
if let tag = tags.first {
|
||||
tag0.text = tag
|
||||
if tags.count > 1 {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22154" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_0" orientation="landscape" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22130"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
@ -60,7 +60,7 @@
|
|||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="102" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AXX-71-9Q6" userLabel="labelInfo">
|
||||
<rect key="frame" x="107" y="120" width="425" height="15"/>
|
||||
<rect key="frame" x="107" y="120" width="31" height="15"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
@ -112,7 +112,7 @@
|
|||
</constraints>
|
||||
</view>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="tag0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qnc-hI-Z9r" customClass="PaddedAndBorderedLabel" customModule="Nextcloud" customModuleProvider="target">
|
||||
<rect key="frame" x="107" y="122.66666666666667" width="36" height="16.333333333333329"/>
|
||||
<rect key="frame" x="107" y="124.66666666666667" width="26" height="14.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" systemColor="systemGrayColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
@ -141,7 +141,7 @@
|
|||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="tag1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jUe-8q-VJd" customClass="PaddedAndBorderedLabel" customModule="Nextcloud" customModuleProvider="target">
|
||||
<rect key="frame" x="148" y="122.66666666666667" width="34" height="16.333333333333329"/>
|
||||
<rect key="frame" x="138" y="124.66666666666667" width="24" height="14.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" systemColor="systemGrayColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
@ -169,12 +169,19 @@
|
|||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" tag="102" contentMode="left" verticalHuggingPriority="251" text="Label" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fu0-vg-6GU" userLabel="labelSubinfo">
|
||||
<rect key="frame" x="138" y="120" width="394" height="15"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</view>
|
||||
<viewLayoutGuide key="safeArea" id="Gu8-oz-zWa"/>
|
||||
<constraints>
|
||||
<constraint firstItem="jUe-8q-VJd" firstAttribute="centerY" secondItem="qnc-hI-Z9r" secondAttribute="centerY" id="2Z4-Yh-1lR"/>
|
||||
<constraint firstAttribute="trailing" secondItem="m2p-oJ-j15" secondAttribute="trailing" constant="90" id="2zI-li-v77"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Fu0-vg-6GU" secondAttribute="bottom" constant="13" id="4sQ-Uf-ovC"/>
|
||||
<constraint firstItem="H4E-G2-C1H" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="-10" id="6fN-Jc-WID"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Egg-cb-EhZ" secondAttribute="bottom" id="81D-sw-EaX"/>
|
||||
<constraint firstItem="AXX-71-9Q6" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="10" id="Bxx-kv-KT3"/>
|
||||
|
@ -191,6 +198,7 @@
|
|||
<constraint firstItem="dgL-g5-Nkc" firstAttribute="centerX" secondItem="yhy-xd-w5C" secondAttribute="centerX" id="VSJ-7R-Srk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="qnc-hI-Z9r" secondAttribute="bottom" constant="9" id="XTs-Qg-kiX"/>
|
||||
<constraint firstItem="7Q9-Tv-9yo" firstAttribute="top" secondItem="w2m-Vw-hpd" secondAttribute="bottom" constant="-10" id="XbB-4a-WpA"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Fu0-vg-6GU" secondAttribute="trailing" constant="137" id="Yv8-Ir-wv3"/>
|
||||
<constraint firstItem="yhy-xd-w5C" firstAttribute="centerY" secondItem="jxV-Pk-fPt" secondAttribute="centerY" id="ZO7-Ny-L3I"/>
|
||||
<constraint firstItem="m2p-oJ-j15" firstAttribute="leading" secondItem="w2m-Vw-hpd" secondAttribute="trailing" constant="10" id="Zyr-qM-9qP"/>
|
||||
<constraint firstAttribute="bottom" secondItem="AXX-71-9Q6" secondAttribute="bottom" constant="13" id="d06-sn-I3Y"/>
|
||||
|
@ -201,7 +209,7 @@
|
|||
<constraint firstItem="w2m-Vw-hpd" firstAttribute="leading" secondItem="jxV-Pk-fPt" secondAttribute="leading" constant="57" id="mBb-ff-7HD"/>
|
||||
<constraint firstItem="w2m-Vw-hpd" firstAttribute="leading" secondItem="7Q9-Tv-9yo" secondAttribute="trailing" constant="-10" id="mon-aq-gcP"/>
|
||||
<constraint firstItem="UtT-L6-mgW" firstAttribute="top" secondItem="jxV-Pk-fPt" secondAttribute="top" constant="13" id="nrY-2F-QZ2"/>
|
||||
<constraint firstAttribute="trailing" secondItem="AXX-71-9Q6" secondAttribute="trailing" constant="137" id="p0M-zU-aDG"/>
|
||||
<constraint firstItem="Fu0-vg-6GU" firstAttribute="leading" secondItem="AXX-71-9Q6" secondAttribute="trailing" id="oEf-wb-lZr"/>
|
||||
<constraint firstItem="w2m-Vw-hpd" firstAttribute="centerY" secondItem="jxV-Pk-fPt" secondAttribute="centerY" id="qKl-4Y-m5t"/>
|
||||
<constraint firstAttribute="trailing" secondItem="yhy-xd-w5C" secondAttribute="trailing" id="s2S-RP-cw5"/>
|
||||
<constraint firstItem="AyA-hP-r6w" firstAttribute="centerY" secondItem="jxV-Pk-fPt" secondAttribute="centerY" id="sJp-0x-bdC"/>
|
||||
|
@ -220,25 +228,26 @@
|
|||
<outlet property="imageSelect" destination="AyA-hP-r6w" id="c1t-yz-HBg"/>
|
||||
<outlet property="imageShared" destination="jc6-Vg-TaS" id="6CL-wO-WaN"/>
|
||||
<outlet property="imageStatus" destination="7Q9-Tv-9yo" id="Qug-Q7-rRZ"/>
|
||||
<outlet property="infoTrailingConstraint" destination="p0M-zU-aDG" id="BJv-hA-VCb"/>
|
||||
<outlet property="labelInfo" destination="AXX-71-9Q6" id="krb-tZ-UQ7"/>
|
||||
<outlet property="labelSubinfo" destination="Fu0-vg-6GU" id="YVU-88-a4q"/>
|
||||
<outlet property="labelTitle" destination="UtT-L6-mgW" id="Xv6-zM-2v1"/>
|
||||
<outlet property="progressView" destination="m2p-oJ-j15" id="yFv-KS-nEy"/>
|
||||
<outlet property="separator" destination="Egg-cb-EhZ" id="uhq-Nc-z8K"/>
|
||||
<outlet property="separatorHeightConstraint" destination="G5S-67-boG" id="B6g-qe-MTb"/>
|
||||
<outlet property="subInfoTrailingConstraint" destination="Yv8-Ir-wv3" id="jkR-EH-b2e"/>
|
||||
<outlet property="tag0" destination="qnc-hI-Z9r" id="6jJ-lV-0ck"/>
|
||||
<outlet property="tag1" destination="jUe-8q-VJd" id="Wcm-rS-rEd"/>
|
||||
<outlet property="titleTrailingConstraint" destination="Tq4-bB-YMV" id="v4n-j5-ZWT"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="128.18590704647679" y="198.40000000000001"/>
|
||||
<point key="canvasLocation" x="127.60663507109004" y="196.92307692307691"/>
|
||||
</collectionViewCell>
|
||||
</objects>
|
||||
<designables>
|
||||
<designable name="jUe-8q-VJd">
|
||||
<size key="intrinsicContentSize" width="34" height="16.333333333333336"/>
|
||||
<size key="intrinsicContentSize" width="24" height="14.333333333333334"/>
|
||||
</designable>
|
||||
<designable name="qnc-hI-Z9r">
|
||||
<size key="intrinsicContentSize" width="36" height="16.333333333333336"/>
|
||||
<size key="intrinsicContentSize" width="26" height="14.333333333333334"/>
|
||||
</designable>
|
||||
</designables>
|
||||
<resources>
|
||||
|
|
|
@ -54,33 +54,33 @@ protocol NCSelectableNavigationView: AnyObject {
|
|||
var tabBarSelect: NCSelectableViewTabBar? { get set }
|
||||
|
||||
func reloadDataSource(withQueryDB: Bool)
|
||||
func setNavigationItems()
|
||||
func setNavigationRightItems()
|
||||
func setNavigationLeftItems()
|
||||
func setNavigationRightItems(enableMoreMenu: Bool)
|
||||
func createMenuActions() -> [UIMenuElement]
|
||||
|
||||
func toggleSelect()
|
||||
func toggleSelect(isOn: Bool?)
|
||||
func onListSelected()
|
||||
func onGridSelected()
|
||||
}
|
||||
|
||||
extension NCSelectableNavigationView {
|
||||
func setNavigationItems() {
|
||||
setNavigationRightItems()
|
||||
}
|
||||
func setNavigationLeftItems() {}
|
||||
|
||||
func saveLayout(_ layoutForView: NCDBLayoutForView) {
|
||||
NCManageDatabase.shared.setLayoutForView(layoutForView: layoutForView)
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource)
|
||||
|
||||
setNavigationRightItems()
|
||||
setNavigationRightItems(enableMoreMenu: true)
|
||||
}
|
||||
|
||||
func toggleSelect() {
|
||||
/// If explicit `isOn` is not set, it will invert `isEditMode`
|
||||
func toggleSelect(isOn: Bool? = nil) {
|
||||
DispatchQueue.main.async {
|
||||
self.isEditMode = !self.isEditMode
|
||||
self.isEditMode = isOn ?? !self.isEditMode
|
||||
self.selectOcId.removeAll()
|
||||
self.selectIndexPath.removeAll()
|
||||
self.setNavigationItems()
|
||||
self.setNavigationLeftItems()
|
||||
self.setNavigationRightItems(enableMoreMenu: true)
|
||||
self.collectionView.reloadData()
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ extension NCSelectableNavigationView {
|
|||
func collectionViewSelectAll() {
|
||||
selectOcId = selectableDataSource.compactMap({ $0.primaryKeyValue })
|
||||
collectionView.reloadData()
|
||||
self.setNavigationRightItems()
|
||||
setNavigationRightItems(enableMoreMenu: true)
|
||||
}
|
||||
|
||||
func tapNotification() {
|
||||
|
|
|
@ -31,6 +31,7 @@ protocol NCCellProtocol {
|
|||
var fileUser: String? { get set }
|
||||
var fileTitleLabel: UILabel? { get set }
|
||||
var fileInfoLabel: UILabel? { get set }
|
||||
var fileSubinfoLabel: UILabel? { get set }
|
||||
var fileProgressView: UIProgressView? { get set }
|
||||
var fileSelectImage: UIImageView? { get set }
|
||||
var fileStatusImage: UIImageView? { get set }
|
||||
|
@ -74,6 +75,10 @@ extension NCCellProtocol {
|
|||
get { return nil }
|
||||
set { }
|
||||
}
|
||||
var fileSubinfoLabel: UILabel? {
|
||||
get { return nil }
|
||||
set { }
|
||||
}
|
||||
var fileProgressView: UIProgressView? {
|
||||
get { return nil }
|
||||
set {}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<rect key="frame" x="177.66666666666666" y="5" width="20" height="20"/>
|
||||
</activityIndicatorView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="s2m-yO-4x0" userLabel="separator">
|
||||
<rect key="frame" x="60" y="30" width="265" height="1"/>
|
||||
<rect key="frame" x="50" y="30" width="275" height="1"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
|
@ -52,7 +52,7 @@
|
|||
<constraint firstItem="qWG-SR-Qly" firstAttribute="centerX" secondItem="EFn-SN-cxu" secondAttribute="centerX" id="18M-RP-YIn"/>
|
||||
<constraint firstItem="EFn-SN-cxu" firstAttribute="trailing" secondItem="TK1-KX-Qe0" secondAttribute="trailing" constant="10" id="PoY-CD-99O"/>
|
||||
<constraint firstAttribute="trailing" secondItem="gzy-cT-Gjn" secondAttribute="trailing" constant="10" id="QzY-ac-CRO"/>
|
||||
<constraint firstItem="EFn-SN-cxu" firstAttribute="leading" secondItem="s2m-yO-4x0" secondAttribute="leading" constant="-10" id="ai4-Qy-YWi"/>
|
||||
<constraint firstItem="EFn-SN-cxu" firstAttribute="leading" secondItem="s2m-yO-4x0" secondAttribute="leading" id="ai4-Qy-YWi"/>
|
||||
<constraint firstItem="gzy-cT-Gjn" firstAttribute="centerY" secondItem="Vin-9E-7nW" secondAttribute="centerY" constant="-30" id="avP-sX-JB5">
|
||||
<variation key="heightClass=compact-widthClass=regular" constant="-15"/>
|
||||
<variation key="heightClass=regular-widthClass=compact" constant="-15"/>
|
||||
|
@ -77,7 +77,7 @@
|
|||
</objects>
|
||||
<resources>
|
||||
<systemColor name="linkColor">
|
||||
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
@ -55,8 +55,8 @@ class NCSectionHeaderMenu: UICollectionReusableView, UIGestureRecognizerDelegate
|
|||
backgroundColor = .clear
|
||||
|
||||
// Gradient
|
||||
gradient.startPoint = CGPoint(x: 0, y: 0.50)
|
||||
gradient.endPoint = CGPoint(x: 0, y: 1)
|
||||
gradient.startPoint = CGPoint(x: 0, y: 0.8)
|
||||
gradient.endPoint = CGPoint(x: 0, y: 0.9)
|
||||
viewRichWorkspace.layer.addSublayer(gradient)
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(touchUpInsideViewRichWorkspace(_:)))
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<rect key="frame" x="0.0" y="318" width="574" height="50"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="pYo-pF-MGv">
|
||||
<rect key="frame" x="5" y="0.0" width="564" height="50"/>
|
||||
<rect key="frame" x="12" y="0.0" width="550" height="50"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
|
@ -29,8 +29,8 @@
|
|||
<constraints>
|
||||
<constraint firstItem="pYo-pF-MGv" firstAttribute="top" secondItem="NC1-5C-E5z" secondAttribute="top" id="PgU-fC-vEG"/>
|
||||
<constraint firstAttribute="height" constant="50" id="eT3-4m-mJ6"/>
|
||||
<constraint firstAttribute="trailing" secondItem="pYo-pF-MGv" secondAttribute="trailing" constant="5" id="nSk-Jr-ufp"/>
|
||||
<constraint firstItem="pYo-pF-MGv" firstAttribute="leading" secondItem="NC1-5C-E5z" secondAttribute="leading" constant="5" id="qoB-Sw-ipc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="pYo-pF-MGv" secondAttribute="trailing" constant="12" id="nSk-Jr-ufp"/>
|
||||
<constraint firstItem="pYo-pF-MGv" firstAttribute="leading" secondItem="NC1-5C-E5z" secondAttribute="leading" constant="12" id="qoB-Sw-ipc"/>
|
||||
<constraint firstAttribute="bottom" secondItem="pYo-pF-MGv" secondAttribute="bottom" id="t4r-dA-VyW"/>
|
||||
</constraints>
|
||||
</view>
|
||||
|
|
|
@ -29,26 +29,38 @@ class NCGridMediaCell: UICollectionViewCell, NCCellProtocol {
|
|||
@IBOutlet weak var imageVisualEffect: UIVisualEffectView!
|
||||
@IBOutlet weak var imageSelect: UIImageView!
|
||||
@IBOutlet weak var imageStatus: UIImageView!
|
||||
@IBOutlet weak var label: UILabel!
|
||||
|
||||
private var objectId: String = ""
|
||||
private var user: String = ""
|
||||
var indexPath = IndexPath()
|
||||
|
||||
var date: Date?
|
||||
private var date: Date?
|
||||
|
||||
var filePreviewImageView: UIImageView? {
|
||||
get { return imageItem }
|
||||
set {}
|
||||
}
|
||||
|
||||
var fileObjectId: String? {
|
||||
get { return objectId }
|
||||
set { objectId = newValue ?? "" }
|
||||
}
|
||||
|
||||
var fileUser: String? {
|
||||
get { return user }
|
||||
set { user = newValue ?? "" }
|
||||
}
|
||||
|
||||
var fileDate: Date? {
|
||||
get { return date }
|
||||
set {
|
||||
date = newValue
|
||||
if let date {
|
||||
label.text = NCUtility().getTitleFromDate(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
initCell()
|
||||
|
@ -63,22 +75,16 @@ class NCGridMediaCell: UICollectionViewCell, NCCellProtocol {
|
|||
imageItem.backgroundColor = .secondarySystemBackground
|
||||
imageStatus.image = nil
|
||||
imageItem.image = nil
|
||||
}
|
||||
|
||||
func selectMode(_ status: Bool) {
|
||||
if status {
|
||||
imageSelect.isHidden = false
|
||||
} else {
|
||||
imageSelect.isHidden = true
|
||||
imageVisualEffect.isHidden = true
|
||||
}
|
||||
imageVisualEffect.alpha = 0.4
|
||||
imageSelect.image = NCImageCache.images.checkedYes
|
||||
imageVisualEffect.isHidden = true
|
||||
imageSelect.isHidden = true
|
||||
}
|
||||
|
||||
func selected(_ status: Bool) {
|
||||
if status {
|
||||
imageSelect.image = NCImageCache.images.checkedYes
|
||||
imageSelect.isHidden = false
|
||||
imageVisualEffect.isHidden = false
|
||||
imageVisualEffect.alpha = 0.4
|
||||
} else {
|
||||
imageSelect.isHidden = true
|
||||
imageVisualEffect.isHidden = true
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina5_5" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -17,13 +16,17 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="220" height="220"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView autoresizesSubviews="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="5Ci-V1-hf5" userLabel="imageItem">
|
||||
<rect key="frame" x="-1" y="-1" width="222" height="222"/>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="5Ci-V1-hf5" userLabel="imageItem">
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="220"/>
|
||||
</imageView>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="a0p-rj-jnV" userLabel="imageStatus">
|
||||
<rect key="frame" x="5" y="192" width="23" height="23"/>
|
||||
<rect key="frame" x="5" y="205" width="10" height="10"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="10" id="iE4-ba-cXj"/>
|
||||
<constraint firstAttribute="width" constant="10" id="uNx-Cr-iBO"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<visualEffectView hidden="YES" opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="r1K-4X-gNd" userLabel="VisualEffect">
|
||||
<visualEffectView hidden="YES" contentMode="scaleAspectFill" translatesAutoresizingMaskIntoConstraints="NO" id="r1K-4X-gNd" userLabel="VisualEffect">
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="220"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="3h4-qt-b9E">
|
||||
<rect key="frame" x="0.0" y="0.0" width="220" height="220"/>
|
||||
|
@ -33,28 +36,37 @@
|
|||
<blurEffect style="extraLight"/>
|
||||
</visualEffectView>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="DHy-Up-3Bh" userLabel="imageSelect">
|
||||
<rect key="frame" x="5" y="5" width="44.5" height="44.5"/>
|
||||
<rect key="frame" x="5" y="5" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="Lkm-Tv-DDQ"/>
|
||||
<constraint firstAttribute="width" constant="20" id="PqO-qT-gfs"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QAA-9f-xaN">
|
||||
<rect key="frame" x="0.0" y="203" width="220" height="17"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</view>
|
||||
<viewLayoutGuide key="safeArea" id="VXh-sQ-LeX"/>
|
||||
<constraints>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="trailing" secondItem="r1K-4X-gNd" secondAttribute="trailing" id="1Hu-GT-dJv"/>
|
||||
<constraint firstItem="DHy-Up-3Bh" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" constant="5" id="1T3-8p-uIW"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="bottom" secondItem="a0p-rj-jnV" secondAttribute="bottom" constant="5" id="2IN-4o-XSp"/>
|
||||
<constraint firstItem="r1K-4X-gNd" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" id="3bv-Dh-iih"/>
|
||||
<constraint firstItem="a0p-rj-jnV" firstAttribute="height" secondItem="5Ci-V1-hf5" secondAttribute="height" multiplier="0.1" constant="1" id="4IJ-uh-zvr"/>
|
||||
<constraint firstItem="DHy-Up-3Bh" firstAttribute="height" secondItem="5Ci-V1-hf5" secondAttribute="height" multiplier="0.2" id="7FN-4V-ZAz"/>
|
||||
<constraint firstItem="a0p-rj-jnV" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" constant="5" id="DYA-5M-RZ8"/>
|
||||
<constraint firstItem="a0p-rj-jnV" firstAttribute="width" secondItem="5Ci-V1-hf5" secondAttribute="width" multiplier="0.1" constant="1" id="DvH-0a-ncn"/>
|
||||
<constraint firstItem="DHy-Up-3Bh" firstAttribute="top" secondItem="VXh-sQ-LeX" secondAttribute="top" constant="5" id="ESV-qE-tbO"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="top" secondItem="VXh-sQ-LeX" secondAttribute="top" constant="-1" id="Ouj-ZD-UFm"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="bottom" secondItem="r1K-4X-gNd" secondAttribute="bottom" id="QAj-Am-H9V"/>
|
||||
<constraint firstItem="r1K-4X-gNd" firstAttribute="top" secondItem="VXh-sQ-LeX" secondAttribute="top" id="Rou-vT-GPt"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="trailing" secondItem="5Ci-V1-hf5" secondAttribute="trailing" constant="-1" id="cHT-cP-NN6"/>
|
||||
<constraint firstItem="VXh-sQ-LeX" firstAttribute="bottom" secondItem="5Ci-V1-hf5" secondAttribute="bottom" constant="-1" id="eEC-eB-alE"/>
|
||||
<constraint firstItem="DHy-Up-3Bh" firstAttribute="width" secondItem="5Ci-V1-hf5" secondAttribute="width" multiplier="0.2" id="ojv-2d-Xmj"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="leading" secondItem="VXh-sQ-LeX" secondAttribute="leading" constant="-1" id="qT3-WD-iTV"/>
|
||||
<constraint firstAttribute="trailing" secondItem="r1K-4X-gNd" secondAttribute="trailing" id="1Hu-GT-dJv"/>
|
||||
<constraint firstItem="DHy-Up-3Bh" firstAttribute="leading" secondItem="vf1-Kf-9uL" secondAttribute="leading" constant="5" id="1T3-8p-uIW"/>
|
||||
<constraint firstItem="QAA-9f-xaN" firstAttribute="trailing" secondItem="vf1-Kf-9uL" secondAttribute="trailing" id="1dV-Sb-7U8"/>
|
||||
<constraint firstAttribute="bottom" secondItem="a0p-rj-jnV" secondAttribute="bottom" constant="5" id="2IN-4o-XSp"/>
|
||||
<constraint firstItem="r1K-4X-gNd" firstAttribute="leading" secondItem="vf1-Kf-9uL" secondAttribute="leading" id="3bv-Dh-iih"/>
|
||||
<constraint firstItem="QAA-9f-xaN" firstAttribute="leading" secondItem="vf1-Kf-9uL" secondAttribute="leading" id="BlV-Di-gR1"/>
|
||||
<constraint firstItem="a0p-rj-jnV" firstAttribute="leading" secondItem="vf1-Kf-9uL" secondAttribute="leading" constant="5" id="DYA-5M-RZ8"/>
|
||||
<constraint firstItem="DHy-Up-3Bh" firstAttribute="top" secondItem="vf1-Kf-9uL" secondAttribute="top" constant="5" id="ESV-qE-tbO"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="top" secondItem="vf1-Kf-9uL" secondAttribute="top" id="Ouj-ZD-UFm"/>
|
||||
<constraint firstAttribute="bottom" secondItem="r1K-4X-gNd" secondAttribute="bottom" id="QAj-Am-H9V"/>
|
||||
<constraint firstItem="r1K-4X-gNd" firstAttribute="top" secondItem="vf1-Kf-9uL" secondAttribute="top" id="Rou-vT-GPt"/>
|
||||
<constraint firstAttribute="trailing" secondItem="5Ci-V1-hf5" secondAttribute="trailing" id="cHT-cP-NN6"/>
|
||||
<constraint firstAttribute="bottom" secondItem="5Ci-V1-hf5" secondAttribute="bottom" id="eEC-eB-alE"/>
|
||||
<constraint firstItem="QAA-9f-xaN" firstAttribute="bottom" secondItem="vf1-Kf-9uL" secondAttribute="bottom" id="mcM-m0-XST"/>
|
||||
<constraint firstItem="5Ci-V1-hf5" firstAttribute="leading" secondItem="vf1-Kf-9uL" secondAttribute="leading" id="qT3-WD-iTV"/>
|
||||
</constraints>
|
||||
<size key="customSize" width="220" height="260"/>
|
||||
<connections>
|
||||
|
@ -62,8 +74,9 @@
|
|||
<outlet property="imageSelect" destination="DHy-Up-3Bh" id="mo9-rP-P4I"/>
|
||||
<outlet property="imageStatus" destination="a0p-rj-jnV" id="6Dg-tf-evd"/>
|
||||
<outlet property="imageVisualEffect" destination="r1K-4X-gNd" id="uf3-P1-F4o"/>
|
||||
<outlet property="label" destination="QAA-9f-xaN" id="PZV-b1-tgG"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="88" y="141.67916041979012"/>
|
||||
<point key="canvasLocation" x="86.956521739130437" y="141.03260869565219"/>
|
||||
</collectionViewCell>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
//
|
||||
// NCMedia+Command.swift
|
||||
// Nextcloud
|
||||
//
|
||||
// Created by Marino Faggiana on 24/02/24.
|
||||
// Copyright © 2024 Marino Faggiana. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import NextcloudKit
|
||||
|
||||
extension NCMedia {
|
||||
@IBAction func selectOrCancelButtonPressed(_ sender: UIButton) {
|
||||
isEditMode = !isEditMode
|
||||
setSelectcancelButton()
|
||||
}
|
||||
|
||||
func setSelectcancelButton() {
|
||||
selectOcId.removeAll()
|
||||
tabBarSelect?.selectCount = selectOcId.count
|
||||
if let visibleCells = self.collectionView?.indexPathsForVisibleItems.compactMap({ self.collectionView?.cellForItem(at: $0) }) {
|
||||
for case let cell as NCGridMediaCell in visibleCells {
|
||||
cell.selected(false)
|
||||
}
|
||||
}
|
||||
if isEditMode {
|
||||
activityIndicatorTrailing.constant = 150
|
||||
selectOrCancelButton.setTitle( NSLocalizedString("_cancel_", comment: ""), for: .normal)
|
||||
selectOrCancelButtonTrailing.constant = 10
|
||||
selectOrCancelButton.isHidden = false
|
||||
menuButton.isHidden = true
|
||||
tabBarSelect?.show()
|
||||
} else {
|
||||
activityIndicatorTrailing.constant = 150
|
||||
selectOrCancelButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
|
||||
selectOrCancelButtonTrailing.constant = 50
|
||||
selectOrCancelButton.isHidden = false
|
||||
menuButton.isHidden = false
|
||||
tabBarSelect?.hide()
|
||||
}
|
||||
}
|
||||
|
||||
func setTitleDate(_ offset: CGFloat = 10) {
|
||||
titleDate?.text = ""
|
||||
if let metadata = metadatas?.first {
|
||||
let contentOffsetY = collectionView.contentOffset.y
|
||||
let top = insetsTop + view.safeAreaInsets.top + offset
|
||||
if insetsTop + view.safeAreaInsets.top + contentOffsetY < 10 {
|
||||
titleDate?.text = utility.getTitleFromDate(metadata.date as Date)
|
||||
return
|
||||
}
|
||||
let point = CGPoint(x: offset, y: top + contentOffsetY)
|
||||
if let indexPath = collectionView.indexPathForItem(at: point) {
|
||||
let cell = self.collectionView(collectionView, cellForItemAt: indexPath) as? NCGridMediaCell
|
||||
if let date = cell?.fileDate {
|
||||
self.titleDate?.text = utility.getTitleFromDate(date)
|
||||
}
|
||||
} else {
|
||||
if offset < 20 {
|
||||
self.setTitleDate(20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setColor() {
|
||||
if isTop {
|
||||
titleDate?.textColor = .label
|
||||
activityIndicator.color = .label
|
||||
selectOrCancelButton.setTitleColor(.label, for: .normal)
|
||||
menuButton.setImage(UIImage(systemName: "ellipsis")?.withTintColor(.label, renderingMode: .alwaysOriginal), for: .normal)
|
||||
gradientView.isHidden = true
|
||||
} else {
|
||||
titleDate?.textColor = .white
|
||||
activityIndicator.color = .white
|
||||
selectOrCancelButton.setTitleColor(.white, for: .normal)
|
||||
menuButton.setImage(UIImage(systemName: "ellipsis")?.withTintColor(.white, renderingMode: .alwaysOriginal), for: .normal)
|
||||
gradientView.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
func createMenu() {
|
||||
var columnCount = NCKeychain().mediaColumnCount
|
||||
let layout = NCKeychain().mediaTypeLayout
|
||||
let layoutTitle = (layout == NCGlobal.shared.mediaLayoutRatio) ? NSLocalizedString("_media_square_", comment: "") : NSLocalizedString("_media_ratio_", comment: "")
|
||||
let layoutImage = (layout == NCGlobal.shared.mediaLayoutRatio) ? UIImage(systemName: "square.grid.3x3") : UIImage(systemName: "rectangle.grid.3x2")
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .phone,
|
||||
(UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) {
|
||||
columnCount += 2
|
||||
}
|
||||
|
||||
if CGFloat(columnCount) >= maxImageGrid - 1 {
|
||||
self.attributesZoomIn = []
|
||||
self.attributesZoomOut = .disabled
|
||||
} else if columnCount <= 1 {
|
||||
self.attributesZoomIn = .disabled
|
||||
self.attributesZoomOut = []
|
||||
} else {
|
||||
self.attributesZoomIn = []
|
||||
self.attributesZoomOut = []
|
||||
}
|
||||
|
||||
let viewFilterMenu = UIMenu(title: "", options: .displayInline, children: [
|
||||
UIAction(title: NSLocalizedString("_media_viewimage_show_", comment: ""), image: UIImage(systemName: "photo")) { _ in
|
||||
self.showOnlyImages = true
|
||||
self.showOnlyVideos = false
|
||||
self.reloadDataSource()
|
||||
},
|
||||
UIAction(title: NSLocalizedString("_media_viewvideo_show_", comment: ""), image: UIImage(systemName: "video")) { _ in
|
||||
self.showOnlyImages = false
|
||||
self.showOnlyVideos = true
|
||||
self.reloadDataSource()
|
||||
},
|
||||
UIAction(title: NSLocalizedString("_media_show_all_", comment: ""), image: UIImage(systemName: "photo.on.rectangle")) { _ in
|
||||
self.showOnlyImages = false
|
||||
self.showOnlyVideos = false
|
||||
self.reloadDataSource()
|
||||
}
|
||||
])
|
||||
let viewLayoutMenu = UIAction(title: layoutTitle, image: layoutImage) { _ in
|
||||
if layout == NCGlobal.shared.mediaLayoutRatio {
|
||||
NCKeychain().mediaTypeLayout = NCGlobal.shared.mediaLayoutSquare
|
||||
} else {
|
||||
NCKeychain().mediaTypeLayout = NCGlobal.shared.mediaLayoutRatio
|
||||
}
|
||||
self.createMenu()
|
||||
self.collectionViewReloadData()
|
||||
}
|
||||
|
||||
let zoomViewMediaFolder = UIMenu(title: "", options: .displayInline, children: [
|
||||
UIMenu(title: NSLocalizedString("_zoom_", comment: ""), children: [
|
||||
UIAction(title: NSLocalizedString("_zoom_out_", comment: ""), image: UIImage(systemName: "minus.magnifyingglass"), attributes: self.attributesZoomOut) { _ in
|
||||
UIView.animate(withDuration: 0.0, animations: {
|
||||
NCKeychain().mediaColumnCount = columnCount + 1
|
||||
self.createMenu()
|
||||
self.collectionViewReloadData()
|
||||
})
|
||||
},
|
||||
UIAction(title: NSLocalizedString("_zoom_in_", comment: ""), image: UIImage(systemName: "plus.magnifyingglass"), attributes: self.attributesZoomIn) { _ in
|
||||
UIView.animate(withDuration: 0.0, animations: {
|
||||
NCKeychain().mediaColumnCount = columnCount - 1
|
||||
self.createMenu()
|
||||
self.collectionViewReloadData()
|
||||
})
|
||||
}
|
||||
]),
|
||||
UIMenu(title: NSLocalizedString("_media_view_options_", comment: ""), children: [viewFilterMenu, viewLayoutMenu]),
|
||||
UIAction(title: NSLocalizedString("_select_media_folder_", comment: ""), image: UIImage(systemName: "folder"), handler: { _ in
|
||||
guard let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController,
|
||||
let viewController = navigationController.topViewController as? NCSelect else { return }
|
||||
viewController.delegate = self
|
||||
viewController.typeOfCommandView = .select
|
||||
viewController.type = "mediaFolder"
|
||||
self.present(navigationController, animated: true)
|
||||
})
|
||||
])
|
||||
|
||||
let playFile = UIAction(title: NSLocalizedString("_play_from_files_", comment: ""), image: UIImage(systemName: "play.circle")) { _ in
|
||||
guard let tabBarController = self.appDelegate.window?.rootViewController as? UITabBarController else { return }
|
||||
self.documentPickerViewController = NCDocumentPickerViewController(tabBarController: tabBarController, isViewerMedia: true, allowsMultipleSelection: false, viewController: self)
|
||||
}
|
||||
|
||||
let playURL = UIAction(title: NSLocalizedString("_play_from_url_", comment: ""), image: UIImage(systemName: "link")) { _ in
|
||||
let alert = UIAlertController(title: NSLocalizedString("_valid_video_url_", comment: ""), message: nil, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: nil))
|
||||
alert.addTextField(configurationHandler: { textField in
|
||||
textField.placeholder = "http://myserver.com/movie.mkv"
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in
|
||||
guard let stringUrl = alert.textFields?.first?.text, !stringUrl.isEmpty, let url = URL(string: stringUrl) else { return }
|
||||
let fileName = url.lastPathComponent
|
||||
let metadata = NCManageDatabase.shared.createMetadata(account: self.activeAccount.account, user: self.activeAccount.user, userId: self.activeAccount.userId, fileName: fileName, fileNameView: fileName, ocId: NSUUID().uuidString, serverUrl: "", urlBase: self.activeAccount.urlBase, url: stringUrl, contentType: "")
|
||||
NCManageDatabase.shared.addMetadata(metadata)
|
||||
NCViewer().view(viewController: self, metadata: metadata, metadatas: [metadata], imageIcon: nil)
|
||||
}))
|
||||
self.present(alert, animated: true)
|
||||
}
|
||||
|
||||
menuButton.menu = UIMenu(title: "", children: [zoomViewMediaFolder, playFile, playURL])
|
||||
}
|
||||
}
|
||||
|
||||
extension NCMedia: NCMediaSelectTabBarDelegate {
|
||||
func delete() {
|
||||
let selectOcId = self.selectOcId.map { $0 }
|
||||
if !selectOcId.isEmpty {
|
||||
let alertController = UIAlertController(
|
||||
title: NSLocalizedString("_delete_selected_photos_", comment: ""),
|
||||
message: "",
|
||||
preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default) { (_: UIAlertAction) in
|
||||
|
||||
Task {
|
||||
var error = NKError()
|
||||
var ocIds: [String] = []
|
||||
for ocId in selectOcId where error == .success {
|
||||
if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
|
||||
error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false)
|
||||
if error == .success {
|
||||
ocIds.append(metadata.ocId)
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocIds, "onlyLocalCache": false, "error": error])
|
||||
}
|
||||
|
||||
self.isEditMode = false
|
||||
self.setSelectcancelButton()
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .default) { (_: UIAlertAction) in })
|
||||
|
||||
present(alertController, animated: true, completion: { })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Zaz-Cl-qpZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="862"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="fF1-wd-0xN">
|
||||
<size key="itemSize" width="0.0" height="0.0"/>
|
||||
|
@ -31,18 +31,79 @@
|
|||
<outlet property="delegate" destination="EFX-fO-Oip" id="s3n-CL-8X2"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7rV-YL-aM7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="150"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="150" id="uAz-q2-42a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rSH-l2-T1a">
|
||||
<rect key="frame" x="10" y="60" width="235" height="40"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="40" id="mcF-qd-xsP"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="9bK-ms-LxX">
|
||||
<rect key="frame" x="255" y="70" width="20" height="20"/>
|
||||
<color key="color" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</activityIndicatorView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Enx-va-Bud">
|
||||
<rect key="frame" x="235" y="65" width="90" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="90" id="N4t-Eb-vDt"/>
|
||||
<constraint firstAttribute="height" constant="30" id="YfM-AZ-ws4"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" title="Title"/>
|
||||
<connections>
|
||||
<action selector="selectOrCancelButtonPressed:" destination="EFX-fO-Oip" eventType="touchUpInside" id="6DJ-3I-rxi"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0dF-cq-2wr">
|
||||
<rect key="frame" x="335" y="65" width="30" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="30" id="6sk-2U-uUH"/>
|
||||
<constraint firstAttribute="height" constant="30" id="SCp-D6-Vad"/>
|
||||
</constraints>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="imageEdgeInsets" minX="4" minY="4" maxX="4" maxY="4"/>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Meh-VD-wWh"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Zaz-Cl-qpZ" firstAttribute="leading" secondItem="Meh-VD-wWh" secondAttribute="leading" id="1bp-sm-u0X"/>
|
||||
<constraint firstItem="Meh-VD-wWh" firstAttribute="trailing" secondItem="Zaz-Cl-qpZ" secondAttribute="trailing" id="aNd-UL-hmu"/>
|
||||
<constraint firstItem="Meh-VD-wWh" firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" constant="-84" id="aNr-tf-2AH"/>
|
||||
<constraint firstItem="Zaz-Cl-qpZ" firstAttribute="leading" secondItem="QEs-gO-Cmp" secondAttribute="leading" id="1bp-sm-u0X"/>
|
||||
<constraint firstAttribute="trailing" secondItem="7rV-YL-aM7" secondAttribute="trailing" id="28S-fu-Qxj"/>
|
||||
<constraint firstItem="7rV-YL-aM7" firstAttribute="top" secondItem="QEs-gO-Cmp" secondAttribute="top" id="4hj-bC-66m"/>
|
||||
<constraint firstItem="9bK-ms-LxX" firstAttribute="leading" secondItem="rSH-l2-T1a" secondAttribute="trailing" constant="10" id="Tyh-tn-Ien"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0dF-cq-2wr" secondAttribute="trailing" constant="10" id="a0d-uf-kZw"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Zaz-Cl-qpZ" secondAttribute="trailing" id="aNd-UL-hmu"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Zaz-Cl-qpZ" secondAttribute="bottom" id="aNr-tf-2AH"/>
|
||||
<constraint firstItem="9bK-ms-LxX" firstAttribute="centerY" secondItem="rSH-l2-T1a" secondAttribute="centerY" id="bAR-d1-xDL"/>
|
||||
<constraint firstItem="rSH-l2-T1a" firstAttribute="top" secondItem="Meh-VD-wWh" secondAttribute="top" constant="10" id="esh-9N-C49"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Enx-va-Bud" secondAttribute="trailing" constant="50" id="exV-eQ-FmL"/>
|
||||
<constraint firstItem="7rV-YL-aM7" firstAttribute="leading" secondItem="QEs-gO-Cmp" secondAttribute="leading" id="gXT-fZ-dAC"/>
|
||||
<constraint firstItem="Enx-va-Bud" firstAttribute="centerY" secondItem="rSH-l2-T1a" secondAttribute="centerY" id="kAD-Y8-RFL"/>
|
||||
<constraint firstItem="Zaz-Cl-qpZ" firstAttribute="top" secondItem="QEs-gO-Cmp" secondAttribute="top" id="nIB-3t-o2I"/>
|
||||
<constraint firstItem="0dF-cq-2wr" firstAttribute="centerY" secondItem="rSH-l2-T1a" secondAttribute="centerY" id="tEU-Gq-Tb8"/>
|
||||
<constraint firstAttribute="trailing" secondItem="9bK-ms-LxX" secondAttribute="trailing" constant="100" id="xM8-MC-pfg"/>
|
||||
<constraint firstAttribute="leading" secondItem="rSH-l2-T1a" secondAttribute="leading" constant="-10" id="xg2-fe-KR9"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="activityIndicator" destination="9bK-ms-LxX" id="dpp-13-6UO"/>
|
||||
<outlet property="activityIndicatorTrailing" destination="xM8-MC-pfg" id="htW-4Z-2Uz"/>
|
||||
<outlet property="collectionView" destination="Zaz-Cl-qpZ" id="8oA-Gx-z7T"/>
|
||||
<outlet property="gradientView" destination="7rV-YL-aM7" id="2QI-sZ-TeA"/>
|
||||
<outlet property="menuButton" destination="0dF-cq-2wr" id="AoT-Kf-eTR"/>
|
||||
<outlet property="selectOrCancelButton" destination="Enx-va-Bud" id="18I-LW-ruL"/>
|
||||
<outlet property="selectOrCancelButtonTrailing" destination="exV-eQ-FmL" id="zrA-Xq-fWL"/>
|
||||
<outlet property="titleDate" destination="rSH-l2-T1a" id="sRl-Sr-fph"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="JJ0-Le-6eT" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
|
|
@ -28,42 +28,42 @@ import RealmSwift
|
|||
class NCMedia: UIViewController, NCEmptyDataSetDelegate {
|
||||
|
||||
@IBOutlet weak var collectionView: UICollectionView!
|
||||
@IBOutlet weak var titleDate: UILabel!
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
@IBOutlet weak var activityIndicatorTrailing: NSLayoutConstraint!
|
||||
@IBOutlet weak var selectOrCancelButton: UIButton!
|
||||
@IBOutlet weak var selectOrCancelButtonTrailing: NSLayoutConstraint!
|
||||
@IBOutlet weak var menuButton: UIButton!
|
||||
@IBOutlet weak var gradientView: UIView!
|
||||
|
||||
var activeAccount = tableAccount()
|
||||
var emptyDataSet: NCEmptyDataSet?
|
||||
var mediaCommandView: NCMediaCommandView?
|
||||
var layout: NCMediaGridLayout!
|
||||
var documentPickerViewController: NCDocumentPickerViewController?
|
||||
var tabBarSelect: NCMediaSelectTabBar?
|
||||
|
||||
let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
|
||||
let utilityFileSystem = NCUtilityFileSystem()
|
||||
let utility = NCUtility()
|
||||
|
||||
let imageCache = NCImageCache.shared
|
||||
var metadatas: ThreadSafeArray<tableMetadata>?
|
||||
let refreshControl = UIRefreshControl()
|
||||
var loadingTask: Task<Void, any Error>?
|
||||
var isTop: Bool = true
|
||||
var isEditMode = false
|
||||
var selectOcId: [String] = []
|
||||
|
||||
var attributesZoomIn: UIMenuElement.Attributes = []
|
||||
var attributesZoomOut: UIMenuElement.Attributes = []
|
||||
let gradient: CAGradientLayer = CAGradientLayer()
|
||||
var showOnlyImages = false
|
||||
var showOnlyVideos = false
|
||||
|
||||
let maxImageGrid: CGFloat = 7
|
||||
var cellHeigth: CGFloat = 0
|
||||
|
||||
var loadingTask: Task<Void, any Error>?
|
||||
|
||||
var lastContentOffsetY: CGFloat = 0
|
||||
var mediaPath = ""
|
||||
|
||||
var timeIntervalSearchNewMedia: TimeInterval = 2.0
|
||||
var timerSearchNewMedia: Timer?
|
||||
|
||||
let insetsTop: CGFloat = 75
|
||||
|
||||
struct cacheImages {
|
||||
static var cellLivePhotoImage = UIImage()
|
||||
static var cellPlayImage = UIImage()
|
||||
static var cellImage = UIImage()
|
||||
}
|
||||
let maxImageGrid: CGFloat = 7
|
||||
var livePhotoImage = UIImage()
|
||||
var playImage = UIImage()
|
||||
var photoImage = UIImage()
|
||||
var videoImage = UIImage()
|
||||
|
||||
// MARK: - View Life Cycle
|
||||
|
||||
|
@ -72,80 +72,122 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate {
|
|||
|
||||
view.backgroundColor = .systemBackground
|
||||
|
||||
layout = NCMediaGridLayout()
|
||||
layout.itemForLine = CGFloat(NCKeychain().mediaItemForLine)
|
||||
layout.sectionHeadersPinToVisibleBounds = true
|
||||
|
||||
collectionView.register(UINib(nibName: "NCGridMediaCell", bundle: nil), forCellWithReuseIdentifier: "gridCell")
|
||||
collectionView.alwaysBounceVertical = true
|
||||
collectionView.contentInset = UIEdgeInsets(top: insetsTop, left: 0, bottom: 50, right: 0)
|
||||
collectionView.backgroundColor = .systemBackground
|
||||
collectionView.prefetchDataSource = self
|
||||
|
||||
let layout = NCMediaLayout()
|
||||
layout.sectionInset = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 2)
|
||||
layout.mediaViewController = self
|
||||
collectionView.collectionViewLayout = layout
|
||||
|
||||
emptyDataSet = NCEmptyDataSet(view: collectionView, offset: 0, delegate: self)
|
||||
|
||||
mediaCommandView = Bundle.main.loadNibNamed("NCMediaCommandView", owner: self, options: nil)?.first as? NCMediaCommandView
|
||||
self.view.addSubview(mediaCommandView!)
|
||||
mediaCommandView?.mediaView = self
|
||||
mediaCommandView?.tabBarController = tabBarController
|
||||
mediaCommandView?.translatesAutoresizingMaskIntoConstraints = false
|
||||
mediaCommandView?.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
|
||||
mediaCommandView?.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
|
||||
mediaCommandView?.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
|
||||
mediaCommandView?.heightAnchor.constraint(equalToConstant: 150).isActive = true
|
||||
tabBarSelect = NCMediaSelectTabBar(tabBarController: self.tabBarController, delegate: self)
|
||||
|
||||
tabBarSelect = NCMediaSelectTabBar(tabBarController: self.tabBarController, delegate: mediaCommandView)
|
||||
livePhotoImage = utility.loadImage(named: "livephoto", color: .white)
|
||||
playImage = utility.loadImage(named: "play.fill", color: .white)
|
||||
|
||||
cacheImages.cellLivePhotoImage = utility.loadImage(named: "livephoto", color: .white)
|
||||
cacheImages.cellPlayImage = utility.loadImage(named: "play.fill", color: .white)
|
||||
titleDate.text = ""
|
||||
|
||||
if let activeAccount = NCManageDatabase.shared.getActiveAccount() { self.mediaPath = activeAccount.mediaPath }
|
||||
selectOrCancelButton.backgroundColor = .clear
|
||||
selectOrCancelButton.layer.cornerRadius = 15
|
||||
selectOrCancelButton.layer.masksToBounds = true
|
||||
selectOrCancelButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
|
||||
selectOrCancelButton.addBlur(style: .systemThinMaterial)
|
||||
|
||||
menuButton.backgroundColor = .clear
|
||||
menuButton.layer.cornerRadius = 15
|
||||
menuButton.layer.masksToBounds = true
|
||||
menuButton.showsMenuAsPrimaryAction = true
|
||||
menuButton.configuration = UIButton.Configuration.plain()
|
||||
menuButton.setImage(UIImage(systemName: "ellipsis"), for: .normal)
|
||||
menuButton.changesSelectionAsPrimaryAction = false
|
||||
menuButton.addBlur(style: .systemThinMaterial)
|
||||
|
||||
gradient.startPoint = CGPoint(x: 0, y: 0.1)
|
||||
gradient.endPoint = CGPoint(x: 0, y: 1)
|
||||
gradient.colors = [UIColor.black.withAlphaComponent(UIAccessibility.isReduceTransparencyEnabled ? 0.8 : 0.4).cgColor, UIColor.clear.cgColor]
|
||||
gradientView.layer.insertSublayer(gradient, at: 0)
|
||||
|
||||
activeAccount = NCManageDatabase.shared.getActiveAccount() ?? tableAccount()
|
||||
|
||||
collectionView.refreshControl = refreshControl
|
||||
refreshControl.action(for: .valueChanged) { _ in
|
||||
DispatchQueue.global().async {
|
||||
self.reloadDataSource()
|
||||
}
|
||||
self.refreshControl.endRefreshing()
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterChangeUser), object: nil, queue: nil) { _ in
|
||||
self.activeAccount = NCManageDatabase.shared.getActiveAccount() ?? tableAccount()
|
||||
if let metadatas = self.metadatas,
|
||||
let metadata = metadatas.first {
|
||||
if metadata.account != self.activeAccount.account {
|
||||
self.metadatas = nil
|
||||
self.collectionViewReloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterCreateMediaCacheEnded), object: nil, queue: nil) { _ in
|
||||
if let metadatas = self.imageCache.initialMetadatas() {
|
||||
self.metadatas = metadatas
|
||||
}
|
||||
self.collectionViewReloadData()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
appDelegate.activeViewController = self
|
||||
|
||||
navigationController?.setMediaAppreance()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(enterForeground(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationWillEnterForeground), object: nil)
|
||||
|
||||
timerSearchNewMedia?.invalidate()
|
||||
timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchMediaUI), userInfo: nil, repeats: false)
|
||||
|
||||
if let metadatas = NCImageCache.shared.initialMetadatas() {
|
||||
self.metadatas = metadatas
|
||||
}
|
||||
|
||||
collectionView.reloadData()
|
||||
startTimer()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
createMenu()
|
||||
|
||||
mediaCommandView?.setTitleDate()
|
||||
mediaCommandView?.createMenu()
|
||||
if imageCache.createMediaCacheInProgress {
|
||||
self.metadatas = nil
|
||||
self.collectionViewReloadData()
|
||||
} else if let metadatas = imageCache.initialMetadatas() {
|
||||
self.metadatas = metadatas
|
||||
self.collectionViewReloadData()
|
||||
} else {
|
||||
DispatchQueue.global().async {
|
||||
self.reloadDataSource()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterDeleteFile), object: nil)
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationWillEnterForeground), object: nil)
|
||||
}
|
||||
|
||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
super.viewWillTransition(to: size, with: coordinator)
|
||||
|
||||
collectionView?.collectionViewLayout.invalidateLayout()
|
||||
mediaCommandView?.setTitleDate()
|
||||
coordinator.animate(alongsideTransition: nil) { _ in
|
||||
self.setTitleDate()
|
||||
}
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
if self.traitCollection.userInterfaceStyle == .dark {
|
||||
return .lightContent
|
||||
} else if let gradient = mediaCommandView?.gradient, gradient.isHidden {
|
||||
} else if isTop {
|
||||
return .darkContent
|
||||
} else {
|
||||
return .lightContent
|
||||
|
@ -154,46 +196,42 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate {
|
|||
|
||||
override func viewWillLayoutSubviews() {
|
||||
super.viewWillLayoutSubviews()
|
||||
|
||||
if let frame = tabBarController?.tabBar.frame {
|
||||
tabBarSelect?.hostingController.view.frame = frame
|
||||
}
|
||||
gradient.frame = gradientView.bounds
|
||||
}
|
||||
|
||||
func startTimer() {
|
||||
// don't start if media chage is in progress
|
||||
if imageCache.createMediaCacheInProgress {
|
||||
return
|
||||
}
|
||||
timerSearchNewMedia?.invalidate()
|
||||
timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchMediaUI), userInfo: nil, repeats: false)
|
||||
}
|
||||
|
||||
// MARK: - NotificationCenter
|
||||
|
||||
@objc func deleteFile(_ notification: NSNotification) {
|
||||
|
||||
guard let userInfo = notification.userInfo as NSDictionary?,
|
||||
let ocIds = userInfo["ocId"] as? [String],
|
||||
let error = userInfo["error"] as? NKError else { return }
|
||||
|
||||
if !ocIds.isEmpty {
|
||||
var items: [IndexPath] = []
|
||||
self.metadatas = self.metadatas?.filter({ !ocIds.contains($0.ocId )})
|
||||
if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
|
||||
for case let cell as NCGridMediaCell in visibleCells {
|
||||
if let ocId = cell.fileObjectId, ocIds.contains(ocId) {
|
||||
items.append(cell.indexPath)
|
||||
}
|
||||
}
|
||||
if !items.isEmpty {
|
||||
collectionView?.deleteItems(at: items)
|
||||
}
|
||||
}
|
||||
self.collectionView?.reloadData()
|
||||
}
|
||||
|
||||
self.reloadDataSource()
|
||||
if error != .success {
|
||||
NCContentPresenter().showError(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func enterForeground(_ notification: NSNotification) {
|
||||
startTimer()
|
||||
}
|
||||
// MARK: - Empty
|
||||
|
||||
func emptyDataSetView(_ view: NCEmptyView) {
|
||||
|
||||
view.emptyImage.image = UIImage(named: "media")?.image(color: .gray, size: UIScreen.main.bounds.width)
|
||||
if loadingTask != nil {
|
||||
if loadingTask != nil || imageCache.createMediaCacheInProgress {
|
||||
view.emptyTitle.text = NSLocalizedString("_search_in_progress_", comment: "")
|
||||
} else {
|
||||
view.emptyTitle.text = NSLocalizedString("_tutorial_photo_view_", comment: "")
|
||||
|
@ -205,38 +243,64 @@ class NCMedia: UIViewController, NCEmptyDataSetDelegate {
|
|||
|
||||
func getImage(metadata: tableMetadata) -> UIImage? {
|
||||
|
||||
if let cachedImage = NCImageCache.shared.getMediaImage(ocId: metadata.ocId, etag: metadata.etag), case let .actual(image) = cachedImage {
|
||||
if let image = imageCache.getMediaImage(ocId: metadata.ocId, etag: metadata.etag) {
|
||||
return image
|
||||
} else if FileManager().fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
|
||||
if let image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
|
||||
NCImageCache.shared.setMediaImage(ocId: metadata.ocId, etag: metadata.etag, image: .actual(image))
|
||||
return image
|
||||
}
|
||||
} else {
|
||||
if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal && (!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)) {
|
||||
if NCNetworking.shared.downloadThumbnailQueue.operations.filter({ ($0 as? NCMediaDownloadThumbnaill)?.metadata.ocId == metadata.ocId }).isEmpty {
|
||||
NCNetworking.shared.downloadThumbnailQueue.addOperation(NCMediaDownloadThumbnaill(metadata: metadata, collectionView: collectionView))
|
||||
}
|
||||
} else if FileManager().fileExists(atPath: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)),
|
||||
let image = UIImage(contentsOfFile: utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
|
||||
imageCache.setMediaSize(ocId: metadata.ocId, etag: metadata.etag, size: image.size)
|
||||
if imageCache.hasMediaImageEnoughSpace() {
|
||||
imageCache.setMediaImage(ocId: metadata.ocId, etag: metadata.etag, image: image, date: metadata.date as Date)
|
||||
}
|
||||
return image
|
||||
} else if metadata.hasPreview && metadata.status == NCGlobal.shared.metadataStatusNormal,
|
||||
(!utilityFileSystem.fileProviderStoragePreviewIconExists(metadata.ocId, etag: metadata.etag)),
|
||||
NCNetworking.shared.downloadThumbnailQueue.operations.filter({ ($0 as? NCMediaDownloadThumbnaill)?.metadata.ocId == metadata.ocId }).isEmpty {
|
||||
NCNetworking.shared.downloadThumbnailQueue.addOperation(NCMediaDownloadThumbnaill(metadata: metadata, media: self))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildMediaPhotoVideo(columnCount: Int) {
|
||||
var pointSize: CGFloat = 0
|
||||
|
||||
switch columnCount {
|
||||
case 0...1: pointSize = 60
|
||||
case 2...3: pointSize = 30
|
||||
case 4...5: pointSize = 25
|
||||
case 6...Int(maxImageGrid): pointSize = 20
|
||||
default: pointSize = 20
|
||||
}
|
||||
if let image = UIImage(systemName: "photo.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize))?.withTintColor(.systemGray4, renderingMode: .alwaysOriginal) {
|
||||
photoImage = image
|
||||
}
|
||||
if let image = UIImage(systemName: "video.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: pointSize))?.withTintColor(.systemGray4, renderingMode: .alwaysOriginal) {
|
||||
videoImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Collection View
|
||||
|
||||
extension NCMedia: UICollectionViewDelegate {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
|
||||
var mediaCell: NCGridMediaCell?
|
||||
if let metadata = self.metadatas?[indexPath.row] {
|
||||
if let visibleCells = self.collectionView?.indexPathsForVisibleItems.compactMap({ self.collectionView?.cellForItem(at: $0) }) {
|
||||
for case let cell as NCGridMediaCell in visibleCells {
|
||||
if cell.fileObjectId == metadata.ocId {
|
||||
mediaCell = cell
|
||||
}
|
||||
}
|
||||
}
|
||||
if isEditMode {
|
||||
if let index = selectOcId.firstIndex(of: metadata.ocId) {
|
||||
selectOcId.remove(at: index)
|
||||
mediaCell?.selected(false)
|
||||
} else {
|
||||
selectOcId.append(metadata.ocId)
|
||||
mediaCell?.selected(true)
|
||||
|
||||
}
|
||||
collectionView.reloadItems(at: [indexPath])
|
||||
tabBarSelect?.selectCount = selectOcId.count
|
||||
} else {
|
||||
// ACTIVE SERVERURL
|
||||
|
@ -249,7 +313,6 @@ extension NCMedia: UICollectionViewDelegate {
|
|||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
|
||||
guard let cell = collectionView.cellForItem(at: indexPath) as? NCGridMediaCell,
|
||||
let metadata = self.metadatas?[indexPath.row] else { return nil }
|
||||
let identifier = indexPath as NSCopying
|
||||
|
@ -263,7 +326,6 @@ extension NCMedia: UICollectionViewDelegate {
|
|||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||
|
||||
animator.addCompletion {
|
||||
if let indexPath = configuration.identifier as? IndexPath {
|
||||
self.collectionView(collectionView, didSelectItemAt: indexPath)
|
||||
|
@ -273,43 +335,37 @@ extension NCMedia: UICollectionViewDelegate {
|
|||
}
|
||||
|
||||
extension NCMedia: UICollectionViewDataSourcePrefetching {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
|
||||
// print("[LOG] n. " + String(indexPaths.count))
|
||||
}
|
||||
}
|
||||
|
||||
extension NCMedia: UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
|
||||
var numberOfItemsInSection = 0
|
||||
|
||||
if let metadatas {
|
||||
numberOfItemsInSection = metadatas.count
|
||||
}
|
||||
|
||||
if let metadatas { numberOfItemsInSection = metadatas.count }
|
||||
if numberOfItemsInSection == 0 {
|
||||
mediaCommandView?.selectOrCancelButton.isHidden = true
|
||||
mediaCommandView?.menuButton.isHidden = false
|
||||
mediaCommandView?.activityIndicatorTrailing.constant = 46
|
||||
selectOrCancelButton.isHidden = true
|
||||
menuButton.isHidden = false
|
||||
gradientView.isHidden = true
|
||||
activityIndicatorTrailing.constant = 50
|
||||
} else if isEditMode {
|
||||
mediaCommandView?.selectOrCancelButton.isHidden = false
|
||||
mediaCommandView?.menuButton.isHidden = true
|
||||
mediaCommandView?.activityIndicatorTrailing.constant = 144
|
||||
selectOrCancelButton.isHidden = false
|
||||
menuButton.isHidden = true
|
||||
activityIndicatorTrailing.constant = 150
|
||||
} else {
|
||||
mediaCommandView?.selectOrCancelButton.isHidden = false
|
||||
mediaCommandView?.menuButton.isHidden = false
|
||||
mediaCommandView?.activityIndicatorTrailing.constant = 144
|
||||
selectOrCancelButton.isHidden = false
|
||||
menuButton.isHidden = false
|
||||
activityIndicatorTrailing.constant = 150
|
||||
}
|
||||
|
||||
emptyDataSet?.numberOfItemsInSection(numberOfItemsInSection, section: section)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { self.setTitleDate() }
|
||||
|
||||
return numberOfItemsInSection
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||
|
||||
guard let metadatas else { return }
|
||||
|
||||
if !collectionView.indexPathsForVisibleItems.contains(indexPath) && indexPath.row < metadatas.count {
|
||||
|
@ -324,22 +380,27 @@ extension NCMedia: UICollectionViewDataSource {
|
|||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
|
||||
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridMediaCell,
|
||||
let metadatas = self.metadatas,
|
||||
let metadata = metadatas[indexPath.row] else { return UICollectionViewCell() }
|
||||
|
||||
self.cellHeigth = cell.frame.size.height
|
||||
|
||||
cell.date = metadata.date as Date
|
||||
cell.fileDate = metadata.date as Date
|
||||
cell.fileObjectId = metadata.ocId
|
||||
cell.indexPath = indexPath
|
||||
cell.fileUser = metadata.ownerId
|
||||
cell.imageStatus.image = nil
|
||||
cell.imageItem.contentMode = .scaleAspectFill
|
||||
|
||||
if let image = getImage(metadata: metadata) {
|
||||
cell.imageItem.backgroundColor = nil
|
||||
cell.imageItem.image = image
|
||||
} else if !metadata.hasPreview {
|
||||
cell.imageItem.backgroundColor = .clear
|
||||
cell.imageItem.contentMode = .center
|
||||
if metadata.isImage {
|
||||
cell.imageItem.image = photoImage
|
||||
} else {
|
||||
cell.imageItem.image = videoImage
|
||||
}
|
||||
}
|
||||
|
||||
// Convert OLD Live Photo
|
||||
|
@ -348,43 +409,68 @@ extension NCMedia: UICollectionViewDataSource {
|
|||
}
|
||||
|
||||
if metadata.isAudioOrVideo {
|
||||
cell.imageStatus.image = cacheImages.cellPlayImage
|
||||
cell.imageStatus.image = playImage
|
||||
} else if metadata.isLivePhoto {
|
||||
cell.imageStatus.image = cacheImages.cellLivePhotoImage
|
||||
cell.imageStatus.image = livePhotoImage
|
||||
} else {
|
||||
cell.imageStatus.image = nil
|
||||
}
|
||||
|
||||
if isEditMode {
|
||||
cell.selectMode(true)
|
||||
if selectOcId.contains(metadata.ocId) {
|
||||
cell.selected(true)
|
||||
} else {
|
||||
cell.selected(false)
|
||||
}
|
||||
if isEditMode, selectOcId.contains(metadata.ocId) {
|
||||
cell.selected(true)
|
||||
} else {
|
||||
cell.selectMode(false)
|
||||
cell.selected(false)
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ScrollView
|
||||
// MARK: -
|
||||
|
||||
extension NCMedia: UICollectionViewDelegateFlowLayout {
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
|
||||
return CGSize(width: collectionView.frame.width, height: 0)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
|
||||
return CGSize(width: collectionView.frame.width, height: 0)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension NCMedia: NCMediaLayoutDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath, columnCount: Int, mediaLayout: String) -> CGSize {
|
||||
let size = CGSize(width: collectionView.frame.width / CGFloat(columnCount), height: collectionView.frame.width / CGFloat(columnCount))
|
||||
if mediaLayout == NCGlobal.shared.mediaLayoutRatio {
|
||||
guard let metadatas = self.metadatas,
|
||||
let metadata = metadatas[indexPath.row] else { return size }
|
||||
|
||||
if metadata.imageSize != CGSize.zero {
|
||||
return metadata.imageSize
|
||||
} else if let size = imageCache.getMediaSize(ocId: metadata.ocId, etag: metadata.etag) {
|
||||
return size
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension NCMedia: UIScrollViewDelegate {
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let metadatas, !metadatas.isEmpty {
|
||||
let isTop = scrollView.contentOffset.y <= -(insetsTop + view.safeAreaInsets.top - 35)
|
||||
mediaCommandView?.setColor(isTop: isTop)
|
||||
isTop = scrollView.contentOffset.y <= -(insetsTop + view.safeAreaInsets.top - 25)
|
||||
setColor()
|
||||
setNeedsStatusBarAppearanceUpdate()
|
||||
if lastContentOffsetY == 0 || lastContentOffsetY + cellHeigth / 2 <= scrollView.contentOffset.y || lastContentOffsetY - cellHeigth / 2 >= scrollView.contentOffset.y {
|
||||
mediaCommandView?.setTitleDate()
|
||||
if lastContentOffsetY == 0 || lastContentOffsetY / 2 <= scrollView.contentOffset.y || lastContentOffsetY / 2 >= scrollView.contentOffset.y {
|
||||
setTitleDate()
|
||||
lastContentOffsetY = scrollView.contentOffset.y
|
||||
}
|
||||
} else {
|
||||
mediaCommandView?.setColor(isTop: true)
|
||||
setColor()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,15 +480,13 @@ extension NCMedia: UIScrollViewDelegate {
|
|||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if !decelerate {
|
||||
if !decelerate {
|
||||
timerSearchNewMedia?.invalidate()
|
||||
timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchMediaUI), userInfo: nil, repeats: false)
|
||||
startTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
timerSearchNewMedia?.invalidate()
|
||||
timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchMediaUI), userInfo: nil, repeats: false)
|
||||
startTimer()
|
||||
}
|
||||
|
||||
func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
|
||||
|
@ -411,17 +495,16 @@ extension NCMedia: UIScrollViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - NCSelect Delegate
|
||||
// MARK: -
|
||||
|
||||
extension NCMedia: NCSelectDelegate {
|
||||
|
||||
func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], indexPath: [IndexPath], overwrite: Bool, copy: Bool, move: Bool) {
|
||||
guard let serverUrl = serverUrl else { return }
|
||||
let home = utilityFileSystem.getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId)
|
||||
mediaPath = serverUrl.replacingOccurrences(of: home, with: "")
|
||||
NCManageDatabase.shared.setAccountMediaPath(mediaPath, account: appDelegate.account)
|
||||
let mediaPath = serverUrl.replacingOccurrences(of: home, with: "")
|
||||
NCManageDatabase.shared.setAccountMediaPath(mediaPath, account: activeAccount.account)
|
||||
activeAccount = NCManageDatabase.shared.getActiveAccount() ?? tableAccount()
|
||||
reloadDataSource()
|
||||
timerSearchNewMedia?.invalidate()
|
||||
timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(self.searchMediaUI), userInfo: nil, repeats: false)
|
||||
startTimer()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,253 +0,0 @@
|
|||
//
|
||||
// NCMediaCommandView.swift
|
||||
// Nextcloud
|
||||
//
|
||||
// Created by Marino Faggiana on 25/01/24.
|
||||
// Copyright © 2024 Marino Faggiana. All rights reserved.
|
||||
//
|
||||
// Author Marino Faggiana <marino.faggiana@nextcloud.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import NextcloudKit
|
||||
|
||||
class NCMediaCommandView: UIView {
|
||||
|
||||
@IBOutlet weak var title: UILabel!
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
@IBOutlet weak var activityIndicatorTrailing: NSLayoutConstraint!
|
||||
@IBOutlet weak var selectOrCancelButton: UIButton!
|
||||
@IBOutlet weak var selectOrCancelButtonTrailing: NSLayoutConstraint!
|
||||
@IBOutlet weak var menuButton: UIButton!
|
||||
|
||||
var mediaView: NCMedia!
|
||||
var tabBarController: UITabBarController?
|
||||
var attributesZoomIn: UIMenuElement.Attributes = []
|
||||
var attributesZoomOut: UIMenuElement.Attributes = []
|
||||
let gradient: CAGradientLayer = CAGradientLayer()
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
title.text = ""
|
||||
|
||||
selectOrCancelButton.backgroundColor = nil
|
||||
selectOrCancelButton.layer.cornerRadius = 15
|
||||
selectOrCancelButton.layer.masksToBounds = true
|
||||
selectOrCancelButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
|
||||
selectOrCancelButton.addBlur(style: .systemThinMaterial)
|
||||
|
||||
menuButton.backgroundColor = nil
|
||||
menuButton.layer.cornerRadius = 15
|
||||
menuButton.layer.masksToBounds = true
|
||||
menuButton.showsMenuAsPrimaryAction = true
|
||||
menuButton.configuration = UIButton.Configuration.plain()
|
||||
menuButton.setImage(UIImage(systemName: "ellipsis"), for: .normal)
|
||||
menuButton.changesSelectionAsPrimaryAction = false
|
||||
menuButton.addBlur(style: .systemThinMaterial)
|
||||
|
||||
gradient.frame = bounds
|
||||
gradient.startPoint = CGPoint(x: 0, y: 0.5)
|
||||
gradient.endPoint = CGPoint(x: 0, y: 1)
|
||||
gradient.colors = [UIColor.black.withAlphaComponent(UIAccessibility.isReduceTransparencyEnabled ? 0.8 : 0.4).cgColor, UIColor.clear.cgColor]
|
||||
layer.insertSublayer(gradient, at: 0)
|
||||
}
|
||||
|
||||
override func layoutSublayers(of layer: CALayer) {
|
||||
super.layoutSublayers(of: layer)
|
||||
gradient.frame = bounds
|
||||
}
|
||||
|
||||
@IBAction func selectOrCancelButtonPressed(_ sender: UIButton) {
|
||||
|
||||
mediaView.isEditMode = !mediaView.isEditMode
|
||||
setSelectcancelButton()
|
||||
}
|
||||
|
||||
func setSelectcancelButton() {
|
||||
|
||||
mediaView.selectOcId.removeAll()
|
||||
mediaView.tabBarSelect?.selectCount = mediaView.selectOcId.count
|
||||
|
||||
if mediaView.isEditMode {
|
||||
selectOrCancelButton.setTitle( NSLocalizedString("_cancel_", comment: ""), for: .normal)
|
||||
selectOrCancelButtonTrailing.constant = 8
|
||||
mediaView.tabBarSelect?.show()
|
||||
} else {
|
||||
selectOrCancelButton.setTitle( NSLocalizedString("_select_", comment: ""), for: .normal)
|
||||
selectOrCancelButtonTrailing.constant = 46
|
||||
mediaView.tabBarSelect?.hide()
|
||||
}
|
||||
|
||||
mediaView.collectionView.reloadData()
|
||||
}
|
||||
|
||||
func setTitleDate() {
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.title.text = ""
|
||||
if let visibleCells = self.mediaView.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.mediaView.collectionView?.cellForItem(at: $0) }) {
|
||||
if let cell = visibleCells.first as? NCGridMediaCell {
|
||||
self.title.text = ""
|
||||
if let date = cell.date {
|
||||
self.title.text = self.mediaView.utility.getTitleFromDate(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setColor(isTop: Bool) {
|
||||
|
||||
if isTop {
|
||||
title.textColor = .label
|
||||
activityIndicator.color = .label
|
||||
selectOrCancelButton.setTitleColor(.label, for: .normal)
|
||||
menuButton.setImage(UIImage(systemName: "ellipsis")?.withTintColor(.label, renderingMode: .alwaysOriginal), for: .normal)
|
||||
gradient.isHidden = true
|
||||
} else {
|
||||
title.textColor = .white
|
||||
activityIndicator.color = .white
|
||||
selectOrCancelButton.setTitleColor(.white, for: .normal)
|
||||
menuButton.setImage(UIImage(systemName: "ellipsis")?.withTintColor(.white, renderingMode: .alwaysOriginal), for: .normal)
|
||||
gradient.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
func createMenu() {
|
||||
|
||||
if let itemForLine = mediaView?.layout.itemForLine, let maxImageGrid = mediaView?.maxImageGrid {
|
||||
if itemForLine >= maxImageGrid - 1 {
|
||||
self.attributesZoomIn = []
|
||||
self.attributesZoomOut = .disabled
|
||||
} else if itemForLine <= 1 {
|
||||
self.attributesZoomIn = .disabled
|
||||
self.attributesZoomOut = []
|
||||
} else {
|
||||
self.attributesZoomIn = []
|
||||
self.attributesZoomOut = []
|
||||
}
|
||||
}
|
||||
|
||||
let topAction = UIMenu(title: "", options: .displayInline, children: [
|
||||
UIMenu(title: NSLocalizedString("_zoom_", comment: ""), children: [
|
||||
UIAction(title: NSLocalizedString("_zoom_out_", comment: ""), image: UIImage(systemName: "minus.magnifyingglass"), attributes: self.attributesZoomOut) { _ in
|
||||
guard let mediaView = self.mediaView else { return }
|
||||
UIView.animate(withDuration: 0.0, animations: {
|
||||
mediaView.layout.itemForLine += 1
|
||||
self.createMenu()
|
||||
mediaView.collectionView.collectionViewLayout.invalidateLayout()
|
||||
NCKeychain().mediaItemForLine = Int(mediaView.layout.itemForLine)
|
||||
})
|
||||
},
|
||||
UIAction(title: NSLocalizedString("_zoom_in_", comment: ""), image: UIImage(systemName: "plus.magnifyingglass"), attributes: self.attributesZoomIn) { _ in
|
||||
UIView.animate(withDuration: 0.0, animations: {
|
||||
self.mediaView.layout.itemForLine -= 1
|
||||
self.createMenu()
|
||||
self.mediaView.collectionView.collectionViewLayout.invalidateLayout()
|
||||
NCKeychain().mediaItemForLine = Int(self.mediaView.layout.itemForLine)
|
||||
})
|
||||
}
|
||||
]),
|
||||
UIMenu(title: NSLocalizedString("_media_view_options_", comment: ""), children: [
|
||||
UIAction(title: NSLocalizedString("_media_viewimage_show_", comment: ""), image: UIImage(systemName: "photo")) { _ in
|
||||
self.mediaView.showOnlyImages = true
|
||||
self.mediaView.showOnlyVideos = false
|
||||
self.mediaView.reloadDataSource()
|
||||
},
|
||||
UIAction(title: NSLocalizedString("_media_viewvideo_show_", comment: ""), image: UIImage(systemName: "video")) { _ in
|
||||
self.mediaView.showOnlyImages = false
|
||||
self.mediaView.showOnlyVideos = true
|
||||
self.mediaView.reloadDataSource()
|
||||
},
|
||||
UIAction(title: NSLocalizedString("_media_show_all_", comment: ""), image: UIImage(systemName: "photo.on.rectangle")) { _ in
|
||||
self.mediaView.showOnlyImages = false
|
||||
self.mediaView.showOnlyVideos = false
|
||||
self.mediaView.reloadDataSource()
|
||||
}
|
||||
]),
|
||||
UIAction(title: NSLocalizedString("_select_media_folder_", comment: ""), image: UIImage(systemName: "folder"), handler: { _ in
|
||||
guard let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as? UINavigationController,
|
||||
let viewController = navigationController.topViewController as? NCSelect else { return }
|
||||
viewController.delegate = self.mediaView
|
||||
viewController.typeOfCommandView = .select
|
||||
viewController.type = "mediaFolder"
|
||||
self.mediaView.present(navigationController, animated: true, completion: nil)
|
||||
})
|
||||
])
|
||||
|
||||
let playFile = UIAction(title: NSLocalizedString("_play_from_files_", comment: ""), image: UIImage(systemName: "play.circle")) { _ in
|
||||
guard let tabBarController = self.mediaView.appDelegate.window?.rootViewController as? UITabBarController else { return }
|
||||
self.mediaView.documentPickerViewController = NCDocumentPickerViewController(tabBarController: tabBarController, isViewerMedia: true, allowsMultipleSelection: false, viewController: self.mediaView)
|
||||
}
|
||||
let playURL = UIAction(title: NSLocalizedString("_play_from_url_", comment: ""), image: UIImage(systemName: "link")) { _ in
|
||||
let alert = UIAlertController(title: NSLocalizedString("_valid_video_url_", comment: ""), message: nil, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: nil))
|
||||
alert.addTextField(configurationHandler: { textField in
|
||||
textField.placeholder = "http://myserver.com/movie.mkv"
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in
|
||||
guard let stringUrl = alert.textFields?.first?.text, !stringUrl.isEmpty, let url = URL(string: stringUrl) else { return }
|
||||
let fileName = url.lastPathComponent
|
||||
let appDelegate = (UIApplication.shared.delegate as? AppDelegate)!
|
||||
let metadata = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: NSUUID().uuidString, serverUrl: "", urlBase: appDelegate.urlBase, url: stringUrl, contentType: "")
|
||||
NCManageDatabase.shared.addMetadata(metadata)
|
||||
NCViewer().view(viewController: self.mediaView, metadata: metadata, metadatas: [metadata], imageIcon: nil)
|
||||
}))
|
||||
self.mediaView.present(alert, animated: true)
|
||||
}
|
||||
|
||||
menuButton.menu = UIMenu(title: "", children: [topAction, playFile, playURL])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NCMediaTabBarSelectDelegate
|
||||
|
||||
extension NCMediaCommandView: NCMediaSelectTabBarDelegate {
|
||||
|
||||
func delete() {
|
||||
|
||||
if !mediaView.selectOcId.isEmpty {
|
||||
let selectOcId = mediaView.selectOcId
|
||||
let alertController = UIAlertController(
|
||||
title: NSLocalizedString("_delete_selected_photos_", comment: ""),
|
||||
message: "",
|
||||
preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", comment: ""), style: .default) { (_: UIAlertAction) in
|
||||
|
||||
Task {
|
||||
var error = NKError()
|
||||
var ocIds: [String] = []
|
||||
for ocId in selectOcId where error == .success {
|
||||
if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
|
||||
error = await NCNetworking.shared.deleteMetadata(metadata, onlyLocalCache: false)
|
||||
if error == .success {
|
||||
ocIds.append(metadata.ocId)
|
||||
}
|
||||
}
|
||||
}
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDeleteFile, userInfo: ["ocId": ocIds, "onlyLocalCache": false, "error": error])
|
||||
}
|
||||
|
||||
self.mediaView.isEditMode = false
|
||||
self.setSelectcancelButton()
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .default) { (_: UIAlertAction) in })
|
||||
|
||||
mediaView.present(alertController, animated: true, completion: { })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="NCMediaCommandView" customModule="Nextcloud" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IxY-xH-yZQ">
|
||||
<rect key="frame" x="8" y="67" width="213" height="40"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="40" id="S6o-Pa-sxy"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="white" translatesAutoresizingMaskIntoConstraints="NO" id="XVj-jD-9KA">
|
||||
<rect key="frame" x="229" y="77" width="20" height="20"/>
|
||||
<color key="color" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</activityIndicatorView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EFV-eb-pFF">
|
||||
<rect key="frame" x="257" y="72" width="90" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="90" id="Hf1-Hv-Jpi"/>
|
||||
<constraint firstAttribute="height" constant="30" id="tTh-bW-DMw"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" title="Title"/>
|
||||
<connections>
|
||||
<action selector="selectOrCancelButtonPressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="0dX-pC-icF"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3qs-Hm-qLL">
|
||||
<rect key="frame" x="355" y="72" width="30" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="l6a-hf-7l2"/>
|
||||
<constraint firstAttribute="width" constant="30" id="uVw-bC-TZq"/>
|
||||
</constraints>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="imageEdgeInsets" minX="4" minY="4" maxX="4" maxY="4"/>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="XVj-jD-9KA" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="4nZ-Ea-KMB"/>
|
||||
<constraint firstItem="3qs-Hm-qLL" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="AgJ-WD-mqU"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="3qs-Hm-qLL" secondAttribute="trailing" constant="8" id="FtF-ES-dyl"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="EFV-eb-pFF" secondAttribute="trailing" constant="46" id="OIB-Zp-XkO"/>
|
||||
<constraint firstItem="IxY-xH-yZQ" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="ZhO-pY-Qwi"/>
|
||||
<constraint firstItem="IxY-xH-yZQ" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="mX3-Fm-K1m"/>
|
||||
<constraint firstItem="EFV-eb-pFF" firstAttribute="centerY" secondItem="IxY-xH-yZQ" secondAttribute="centerY" id="ozT-m6-dct"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="XVj-jD-9KA" secondAttribute="trailing" constant="144" id="uEq-dt-udC"/>
|
||||
<constraint firstItem="XVj-jD-9KA" firstAttribute="leading" secondItem="IxY-xH-yZQ" secondAttribute="trailing" constant="8" id="ztz-0d-9Mr"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="activityIndicator" destination="XVj-jD-9KA" id="cSB-RJ-RCZ"/>
|
||||
<outlet property="activityIndicatorTrailing" destination="uEq-dt-udC" id="pvB-8X-Jpb"/>
|
||||
<outlet property="menuButton" destination="3qs-Hm-qLL" id="7uo-w1-pml"/>
|
||||
<outlet property="selectOrCancelButton" destination="EFV-eb-pFF" id="2ve-si-IjY"/>
|
||||
<outlet property="selectOrCancelButtonTrailing" destination="OIB-Zp-XkO" id="2gp-In-Snh"/>
|
||||
<outlet property="title" destination="IxY-xH-yZQ" id="ZNZ-Jy-JbH"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="140" y="148"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -26,112 +26,132 @@ import NextcloudKit
|
|||
|
||||
extension NCMedia {
|
||||
|
||||
func getPredicate(showAll: Bool = false) -> NSPredicate {
|
||||
|
||||
let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: appDelegate.urlBase, userId: appDelegate.userId) + mediaPath
|
||||
|
||||
if showAll {
|
||||
return NSPredicate(format: NCImageCache.shared.showAllPredicateMediaString, appDelegate.account, startServerUrl)
|
||||
} else if showOnlyImages {
|
||||
return NSPredicate(format: NCImageCache.shared.showOnlyPredicateMediaString, appDelegate.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue)
|
||||
} else if showOnlyVideos {
|
||||
return NSPredicate(format: NCImageCache.shared.showOnlyPredicateMediaString, appDelegate.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue)
|
||||
} else {
|
||||
return NSPredicate(format: NCImageCache.shared.showBothPredicateMediaString, appDelegate.account, startServerUrl)
|
||||
}
|
||||
func reloadDataSource() {
|
||||
self.metadatas = imageCache.getMediaMetadatas(account: activeAccount.account, predicate: self.getPredicate())
|
||||
self.collectionViewReloadData()
|
||||
}
|
||||
|
||||
@objc func reloadDataSource() {
|
||||
guard !appDelegate.account.isEmpty else { return }
|
||||
|
||||
self.metadatas = NCImageCache.shared.getMediaMetadatas(account: self.appDelegate.account, predicate: self.getPredicate())
|
||||
DispatchQueue.main.async {
|
||||
self.collectionView?.reloadData()
|
||||
self.mediaCommandView?.setTitleDate()
|
||||
func collectionViewReloadData() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
self.collectionView.reloadData()
|
||||
self.setTitleDate()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Search media
|
||||
|
||||
@objc func searchMediaUI() {
|
||||
|
||||
var lessDate: Date?
|
||||
var greaterDate: Date?
|
||||
let firstMetadataDate = metadatas?.first?.date as? Date
|
||||
let lastMetadataDate = metadatas?.last?.date as? Date
|
||||
let countMetadatas = self.metadatas?.count ?? 0
|
||||
|
||||
guard loadingTask == nil, !isEditMode else {
|
||||
guard loadingTask == nil,
|
||||
!isEditMode,
|
||||
self.viewIfLoaded?.window != nil,
|
||||
let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) })
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
|
||||
// first date
|
||||
let firstCellDate = (visibleCells.first as? NCGridMediaCell)?.date
|
||||
if firstCellDate == firstMetadataDate {
|
||||
// first date
|
||||
let firstCellDate = (visibleCells.first as? NCGridMediaCell)?.fileDate
|
||||
if firstCellDate == firstMetadataDate {
|
||||
lessDate = Date.distantFuture
|
||||
} else {
|
||||
if let date = firstCellDate {
|
||||
lessDate = Calendar.current.date(byAdding: .second, value: 1, to: date)!
|
||||
} else {
|
||||
lessDate = Date.distantFuture
|
||||
} else {
|
||||
if let date = firstCellDate {
|
||||
lessDate = Calendar.current.date(byAdding: .second, value: 1, to: date)!
|
||||
} else {
|
||||
lessDate = Date.distantFuture
|
||||
}
|
||||
}
|
||||
// last date
|
||||
let lastCellDate = (visibleCells.last as? NCGridMediaCell)?.date
|
||||
if lastCellDate == lastMetadataDate {
|
||||
}
|
||||
// last date
|
||||
let lastCellDate = (visibleCells.last as? NCGridMediaCell)?.fileDate
|
||||
if lastCellDate == lastMetadataDate {
|
||||
greaterDate = Date.distantPast
|
||||
} else {
|
||||
if let date = lastCellDate {
|
||||
greaterDate = Calendar.current.date(byAdding: .second, value: -1, to: date)!
|
||||
} else {
|
||||
greaterDate = Date.distantPast
|
||||
} else {
|
||||
if let date = lastCellDate {
|
||||
greaterDate = Calendar.current.date(byAdding: .second, value: -1, to: date)!
|
||||
} else {
|
||||
greaterDate = Date.distantPast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let lessDate, let greaterDate {
|
||||
mediaCommandView?.activityIndicator.startAnimating()
|
||||
loadingTask = Task.detached {
|
||||
await self.collectionView.reloadData()
|
||||
let results = await self.searchMedia(account: self.appDelegate.account, lessDate: lessDate, greaterDate: greaterDate)
|
||||
print("Media results changed items: \(results.isChanged)")
|
||||
await self.mediaCommandView?.activityIndicator.stopAnimating()
|
||||
if lessDate == Date.distantFuture,
|
||||
greaterDate == Date.distantPast,
|
||||
(self.metadatas?.count ?? 0) > visibleCells.count {
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media oops. something is bad (distantFuture, distantPast): \(self.activeAccount.account), \(self.appDelegate.account), \(self.metadatas?.count ?? 0)")
|
||||
return
|
||||
}
|
||||
|
||||
if let lessDate, let greaterDate {
|
||||
activityIndicator.startAnimating()
|
||||
loadingTask = Task.detached {
|
||||
if countMetadatas == 0 {
|
||||
await self.collectionViewReloadData()
|
||||
}
|
||||
let results = await self.searchMedia(lessDate: lessDate, greaterDate: greaterDate)
|
||||
if results.error == .success {
|
||||
Task { @MainActor in
|
||||
self.loadingTask = nil
|
||||
}
|
||||
if results.error != .success {
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(results.error.errorCode) " + results.error.errorDescription)
|
||||
} else if results.error == .success, results.lessDate == Date.distantFuture, results.greaterDate == Date.distantPast, !results.isChanged, results.metadatasCount == 0 {
|
||||
Task { @MainActor in
|
||||
if results.lessDate == Date.distantFuture, results.greaterDate == Date.distantPast, !results.isChanged, results.metadatasCount == 0 {
|
||||
self.metadatas = nil
|
||||
self.collectionViewReloadData()
|
||||
print("searchMediaUI: metadatacount 0")
|
||||
} else if results.isChanged {
|
||||
self.reloadDataSource()
|
||||
print("searchMediaUI: changed")
|
||||
} else {
|
||||
print("searchMediaUI: nothing")
|
||||
}
|
||||
}
|
||||
if results.isChanged {
|
||||
await self.reloadDataSource()
|
||||
} else {
|
||||
await self.collectionView.reloadData()
|
||||
}
|
||||
} else {
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Media search new media error code \(results.error.errorCode) " + results.error.errorDescription)
|
||||
}
|
||||
Task { @MainActor in
|
||||
self.loadingTask = nil
|
||||
self.activityIndicator.stopAnimating()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func searchMedia(account: String, lessDate: Date, greaterDate: Date, limit: Int = 120, timeout: TimeInterval = 60) async -> (account: String, lessDate: Date?, greaterDate: Date?, metadatasCount: Int, isChanged: Bool, error: NKError) {
|
||||
private func searchMedia(lessDate: Date, greaterDate: Date, limit: Int = 300, timeout: TimeInterval = 120) async -> (lessDate: Date?, greaterDate: Date?, metadatasCount: Int, isChanged: Bool, error: NKError) {
|
||||
|
||||
guard let mediaPath = NCManageDatabase.shared.getActiveAccount()?.mediaPath else {
|
||||
return(account, lessDate, greaterDate, 0, false, NKError())
|
||||
return(lessDate, greaterDate, 0, false, NKError())
|
||||
}
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("Start searchMedia with lessDate \(lessDate), greaterDate \(greaterDate)")
|
||||
let options = NKRequestOptions(timeout: timeout, queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
|
||||
let results = await NextcloudKit.shared.searchMedia(path: mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: NCKeychain().showHiddenFiles, includeHiddenFiles: [], options: options)
|
||||
|
||||
if results.account == account, results.error == .success {
|
||||
if results.account != self.activeAccount.account {
|
||||
let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "User changed")
|
||||
return(lessDate, greaterDate, 0, false, error)
|
||||
} else if results.error == .success {
|
||||
let metadatas = await NCManageDatabase.shared.convertFilesToMetadatas(results.files, useMetadataFolder: false).metadatas
|
||||
var predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
|
||||
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, self.getPredicate(showAll: true)])
|
||||
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, getPredicate(showAll: true)])
|
||||
let resultsUpdate = NCManageDatabase.shared.updateMetadatas(metadatas, predicate: predicate)
|
||||
let isChaged: Bool = resultsUpdate.metadatasChanged || resultsUpdate.metadatasChangedCount != 0
|
||||
return(account, lessDate, greaterDate, metadatas.count, isChaged, results.error)
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("End searchMedia UpdateMetadatas with metadatasChanged \(resultsUpdate.metadatasChanged), ChangedCount \(resultsUpdate.metadatasChangedCount)")
|
||||
return(lessDate, greaterDate, metadatas.count, isChaged, results.error)
|
||||
} else {
|
||||
return(account, lessDate, greaterDate, 0, false, results.error)
|
||||
return(lessDate, greaterDate, 0, false, results.error)
|
||||
}
|
||||
}
|
||||
|
||||
private func getPredicate(showAll: Bool = false) -> NSPredicate {
|
||||
let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) + activeAccount.mediaPath
|
||||
|
||||
if showAll {
|
||||
return NSPredicate(format: imageCache.showAllPredicateMediaString, activeAccount.account, startServerUrl)
|
||||
} else if showOnlyImages {
|
||||
return NSPredicate(format: imageCache.showOnlyPredicateMediaString, activeAccount.account, startServerUrl, NKCommon.TypeClassFile.image.rawValue)
|
||||
} else if showOnlyVideos {
|
||||
return NSPredicate(format: imageCache.showOnlyPredicateMediaString, activeAccount.account, startServerUrl, NKCommon.TypeClassFile.video.rawValue)
|
||||
} else {
|
||||
return NSPredicate(format: imageCache.showBothPredicateMediaString, activeAccount.account, startServerUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,33 +28,34 @@ import Queuer
|
|||
class NCMediaDownloadThumbnaill: ConcurrentOperation {
|
||||
|
||||
var metadata: tableMetadata
|
||||
var collectionView: UICollectionView?
|
||||
var media: NCMedia
|
||||
var fileNamePath: String
|
||||
var fileNamePreviewLocalPath: String
|
||||
var fileNameIconLocalPath: String
|
||||
let utilityFileSystem = NCUtilityFileSystem()
|
||||
|
||||
init(metadata: tableMetadata, collectionView: UICollectionView?) {
|
||||
init(metadata: tableMetadata, media: NCMedia) {
|
||||
self.metadata = tableMetadata.init(value: metadata)
|
||||
self.collectionView = collectionView
|
||||
self.media = media
|
||||
self.fileNamePath = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId)
|
||||
self.fileNamePreviewLocalPath = utilityFileSystem.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)
|
||||
self.fileNameIconLocalPath = utilityFileSystem.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)
|
||||
}
|
||||
|
||||
override func start() {
|
||||
|
||||
guard !isCancelled else { return self.finish() }
|
||||
|
||||
var etagResource: String?
|
||||
let sizePreview = NCUtility().getSizePreview(width: metadata.width, height: metadata.height)
|
||||
|
||||
if FileManager.default.fileExists(atPath: fileNameIconLocalPath) && FileManager.default.fileExists(atPath: fileNamePreviewLocalPath) {
|
||||
etagResource = metadata.etagResource
|
||||
}
|
||||
|
||||
NextcloudKit.shared.downloadPreview(fileNamePathOrFileId: fileNamePath,
|
||||
fileNamePreviewLocalPath: fileNamePreviewLocalPath,
|
||||
widthPreview: NCGlobal.shared.sizePreview,
|
||||
heightPreview: NCGlobal.shared.sizePreview,
|
||||
widthPreview: Int(sizePreview.width),
|
||||
heightPreview: Int(sizePreview.height),
|
||||
fileNameIconLocalPath: fileNameIconLocalPath,
|
||||
sizeIcon: NCGlobal.shared.sizeIcon,
|
||||
etag: etagResource,
|
||||
|
@ -63,7 +64,7 @@ class NCMediaDownloadThumbnaill: ConcurrentOperation {
|
|||
if error == .success, let image = imagePreview {
|
||||
NCManageDatabase.shared.setMetadataEtagResource(ocId: self.metadata.ocId, etagResource: etag)
|
||||
DispatchQueue.main.async {
|
||||
if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
|
||||
if let visibleCells = self.media.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.media.collectionView?.cellForItem(at: $0) }) {
|
||||
for case let cell as NCGridMediaCell in visibleCells {
|
||||
if cell.fileObjectId == self.metadata.ocId, let filePreviewImageView = cell.filePreviewImageView {
|
||||
UIView.transition(with: filePreviewImageView,
|
||||
|
@ -76,9 +77,16 @@ class NCMediaDownloadThumbnaill: ConcurrentOperation {
|
|||
}
|
||||
}
|
||||
}
|
||||
NCImageCache.shared.setMediaImage(ocId: self.metadata.ocId, etag: self.metadata.etag, image: .actual(image))
|
||||
NCImageCache.shared.setMediaSize(ocId: self.metadata.ocId, etag: self.metadata.etag, size: image.size)
|
||||
}
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
||||
override func finish(success: Bool = true) {
|
||||
super.finish(success: success)
|
||||
if (metadata.width == 0 && metadata.height == 0) || (NCNetworking.shared.downloadThumbnailQueue.operationCount == 0) {
|
||||
self.media.collectionViewReloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
//
|
||||
// NCMediaGridLayout.swift
|
||||
// Nextcloud
|
||||
//
|
||||
// Created by Marino Faggiana on 27/01/24.
|
||||
// Copyright © 2024 Marino Faggiana. All rights reserved.
|
||||
//
|
||||
// Author Marino Faggiana <marino.faggiana@nextcloud.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class NCMediaGridLayout: UICollectionViewFlowLayout {
|
||||
|
||||
var marginLeftRight: CGFloat = 2
|
||||
var itemForLine: CGFloat = 3
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
sectionHeadersPinToVisibleBounds = false
|
||||
|
||||
minimumInteritemSpacing = 0
|
||||
minimumLineSpacing = marginLeftRight
|
||||
|
||||
self.scrollDirection = .vertical
|
||||
self.sectionInset = UIEdgeInsets(top: 0, left: marginLeftRight, bottom: 0, right: marginLeftRight)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var itemSize: CGSize {
|
||||
get {
|
||||
if let collectionView = collectionView {
|
||||
|
||||
let itemWidth: CGFloat = (collectionView.frame.width - marginLeftRight * 2 - marginLeftRight * (itemForLine - 1)) / itemForLine
|
||||
let itemHeight: CGFloat = itemWidth
|
||||
|
||||
return CGSize(width: itemWidth, height: itemHeight)
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return CGSize(width: 100, height: 100)
|
||||
}
|
||||
set {
|
||||
super.itemSize = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
|
||||
return proposedContentOffset
|
||||
}
|
||||
}
|
||||
|
||||
extension NCMedia: UICollectionViewDelegateFlowLayout {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
|
||||
return CGSize(width: collectionView.frame.width, height: 0)
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
|
||||
return CGSize(width: collectionView.frame.width, height: 0)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
//
|
||||
// NCMediaLayout.swift
|
||||
//
|
||||
// Created by Marino Faggiana on 26/02/24.
|
||||
// Based on CHTCollectionViewWaterfallLayout by Nelson Tai
|
||||
// Copyright © 2024 Marino Faggiana. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public let collectionViewMediaElementKindSectionHeader = "collectionViewMediaElementKindSectionHeader"
|
||||
public let collectionViewMediaElementKindSectionFooter = "collectionViewMediaElementKindSectionFooter"
|
||||
|
||||
protocol NCMediaLayoutDelegate: UICollectionViewDelegate {
|
||||
func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath, columnCount: Int, mediaLayout: String) -> CGSize
|
||||
}
|
||||
|
||||
public class NCMediaLayout: UICollectionViewLayout {
|
||||
|
||||
// MARK: - Private constants
|
||||
/// How many items to be union into a single rectangle
|
||||
private let unionSize = 20
|
||||
|
||||
// MARK: - Public Properties
|
||||
public var columnCount: Int = 0 {
|
||||
didSet {
|
||||
invalidateIfNotEqual(oldValue, newValue: columnCount)
|
||||
}
|
||||
}
|
||||
public var minimumColumnSpacing: Float = 2.0 {
|
||||
didSet {
|
||||
invalidateIfNotEqual(oldValue, newValue: minimumColumnSpacing)
|
||||
}
|
||||
}
|
||||
public var minimumInteritemSpacing: Float = 2.0 {
|
||||
didSet {
|
||||
invalidateIfNotEqual(oldValue, newValue: minimumInteritemSpacing)
|
||||
}
|
||||
}
|
||||
public var headerHeight: Float = 0.0 {
|
||||
didSet {
|
||||
invalidateIfNotEqual(oldValue, newValue: headerHeight)
|
||||
}
|
||||
}
|
||||
public var footerHeight: Float = 0.0 {
|
||||
didSet {
|
||||
invalidateIfNotEqual(oldValue, newValue: footerHeight)
|
||||
}
|
||||
}
|
||||
public var headerInset: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
invalidateIfNotEqual(oldValue, newValue: headerInset)
|
||||
}
|
||||
}
|
||||
public var footerInset: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
invalidateIfNotEqual(oldValue, newValue: footerInset)
|
||||
}
|
||||
}
|
||||
public var sectionInset: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
invalidateIfNotEqual(oldValue, newValue: sectionInset)
|
||||
}
|
||||
}
|
||||
var mediaViewController: NCMedia?
|
||||
var mediaLayout = ""
|
||||
|
||||
public override var collectionViewContentSize: CGSize {
|
||||
let numberOfSections = collectionView?.numberOfSections
|
||||
if numberOfSections == 0 {
|
||||
return CGSize.zero
|
||||
}
|
||||
|
||||
var contentSize = collectionView?.bounds.size
|
||||
contentSize?.height = CGFloat(columnHeights[0])
|
||||
|
||||
return contentSize!
|
||||
}
|
||||
|
||||
// MARK: - Private Properties
|
||||
private weak var delegate: NCMediaLayoutDelegate? {
|
||||
return collectionView?.delegate as? NCMediaLayoutDelegate
|
||||
}
|
||||
private var columnHeights = [Float]()
|
||||
private var sectionItemAttributes = [[UICollectionViewLayoutAttributes]]()
|
||||
private var allItemAttributes = [UICollectionViewLayoutAttributes]()
|
||||
private var headersAttribute = [Int: UICollectionViewLayoutAttributes]()
|
||||
private var footersAttribute = [Int: UICollectionViewLayoutAttributes]()
|
||||
private var unionRects = [CGRect]()
|
||||
|
||||
// MARK: - UICollectionViewLayout Methods
|
||||
public override func prepare() {
|
||||
super.prepare()
|
||||
|
||||
guard let numberOfSections = collectionView?.numberOfSections,
|
||||
let collectionView = collectionView,
|
||||
let delegate = delegate else { return }
|
||||
|
||||
mediaLayout = NCKeychain().mediaTypeLayout
|
||||
columnCount = NCKeychain().mediaColumnCount
|
||||
mediaViewController?.buildMediaPhotoVideo(columnCount: columnCount)
|
||||
if UIDevice.current.userInterfaceIdiom == .phone,
|
||||
(UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) {
|
||||
columnCount += 2
|
||||
}
|
||||
|
||||
// Initialize variables
|
||||
headersAttribute.removeAll(keepingCapacity: false)
|
||||
footersAttribute.removeAll(keepingCapacity: false)
|
||||
unionRects.removeAll(keepingCapacity: false)
|
||||
columnHeights.removeAll(keepingCapacity: false)
|
||||
allItemAttributes.removeAll(keepingCapacity: false)
|
||||
sectionItemAttributes.removeAll(keepingCapacity: false)
|
||||
|
||||
for _ in 0..<columnCount {
|
||||
self.columnHeights.append(0)
|
||||
}
|
||||
|
||||
// Create attributes
|
||||
var top: Float = 0
|
||||
var attributes: UICollectionViewLayoutAttributes
|
||||
|
||||
for section in 0..<numberOfSections {
|
||||
/*
|
||||
* 1. Get section-specific metrics (minimumInteritemSpacing, sectionInset)
|
||||
*/
|
||||
let width = Float(collectionView.frame.size.width - sectionInset.left - sectionInset.right)
|
||||
let itemWidth = floorf((width - Float(columnCount - 1) * Float(minimumColumnSpacing)) / Float(columnCount))
|
||||
|
||||
/*
|
||||
* 2. Section header
|
||||
*/
|
||||
top += Float(headerInset.top)
|
||||
|
||||
if headerHeight > 0 {
|
||||
attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: collectionViewMediaElementKindSectionHeader, with: NSIndexPath(item: 0, section: section) as IndexPath)
|
||||
attributes.frame = CGRect(x: headerInset.left, y: CGFloat(top), width: collectionView.frame.size.width - (headerInset.left + headerInset.right), height: CGFloat(headerHeight))
|
||||
|
||||
headersAttribute[section] = attributes
|
||||
allItemAttributes.append(attributes)
|
||||
|
||||
top = Float(attributes.frame.maxY) + Float(headerInset.bottom)
|
||||
}
|
||||
|
||||
top += Float(sectionInset.top)
|
||||
for idx in 0..<columnCount {
|
||||
columnHeights[idx] = top
|
||||
}
|
||||
|
||||
/*
|
||||
* 3. Section items
|
||||
*/
|
||||
let itemCount = collectionView.numberOfItems(inSection: section)
|
||||
var itemAttributes = [UICollectionViewLayoutAttributes]()
|
||||
|
||||
// Item will be put into shortest column.
|
||||
for idx in 0..<itemCount {
|
||||
let indexPath = NSIndexPath(item: idx, section: section)
|
||||
let columnIndex = shortestColumnIndex()
|
||||
|
||||
let xOffset = Float(sectionInset.left) + Float(itemWidth + minimumColumnSpacing) * Float(columnIndex)
|
||||
let yOffset = columnHeights[columnIndex]
|
||||
let itemSize = delegate.collectionView(collectionView, layout: self, sizeForItemAtIndexPath: indexPath, columnCount: self.columnCount, mediaLayout: self.mediaLayout)
|
||||
var itemHeight: Float = 0.0
|
||||
if itemSize.height > 0 && itemSize.width > 0 {
|
||||
itemHeight = Float(itemSize.height) * itemWidth / Float(itemSize.width)
|
||||
}
|
||||
|
||||
attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
|
||||
attributes.frame = CGRect(x: CGFloat(xOffset), y: CGFloat(yOffset), width: CGFloat(itemWidth), height: CGFloat(itemHeight))
|
||||
itemAttributes.append(attributes)
|
||||
allItemAttributes.append(attributes)
|
||||
columnHeights[columnIndex] = Float(attributes.frame.maxY) + minimumInteritemSpacing
|
||||
}
|
||||
|
||||
sectionItemAttributes.append(itemAttributes)
|
||||
|
||||
/*
|
||||
* 4. Section footer
|
||||
*/
|
||||
let columnIndex = longestColumnIndex()
|
||||
top = columnHeights[columnIndex] - minimumInteritemSpacing + Float(sectionInset.bottom)
|
||||
top += Float(footerInset.top)
|
||||
|
||||
if footerHeight > 0 {
|
||||
attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: collectionViewMediaElementKindSectionFooter, with: NSIndexPath(item: 0, section: section) as IndexPath)
|
||||
attributes.frame = CGRect(x: footerInset.left, y: CGFloat(top), width: collectionView.frame.size.width - (footerInset.left + footerInset.right), height: CGFloat(footerHeight))
|
||||
|
||||
footersAttribute[section] = attributes
|
||||
allItemAttributes.append(attributes)
|
||||
|
||||
top = Float(attributes.frame.maxY) + Float(footerInset.bottom)
|
||||
}
|
||||
|
||||
for idx in 0..<columnCount {
|
||||
columnHeights[idx] = top
|
||||
}
|
||||
}
|
||||
|
||||
// Build union rects
|
||||
var idx = 0
|
||||
let itemCounts = allItemAttributes.count
|
||||
|
||||
while idx < itemCounts {
|
||||
let rect1 = allItemAttributes[idx].frame
|
||||
idx = min(idx + unionSize, itemCounts) - 1
|
||||
let rect2 = allItemAttributes[idx].frame
|
||||
unionRects.append(rect1.union(rect2))
|
||||
idx += 1
|
||||
}
|
||||
}
|
||||
|
||||
public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
if indexPath.section >= sectionItemAttributes.count {
|
||||
return nil
|
||||
}
|
||||
|
||||
if indexPath.item >= sectionItemAttributes[indexPath.section].count {
|
||||
return nil
|
||||
}
|
||||
|
||||
return sectionItemAttributes[indexPath.section][indexPath.item]
|
||||
}
|
||||
|
||||
public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
var attribute: UICollectionViewLayoutAttributes?
|
||||
|
||||
if elementKind == collectionViewMediaElementKindSectionHeader {
|
||||
attribute = headersAttribute[indexPath.section]
|
||||
} else if elementKind == collectionViewMediaElementKindSectionFooter {
|
||||
attribute = footersAttribute[indexPath.section]
|
||||
}
|
||||
|
||||
return attribute
|
||||
}
|
||||
|
||||
public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
var begin: Int = 0
|
||||
var end: Int = unionRects.count
|
||||
var attrs = [UICollectionViewLayoutAttributes]()
|
||||
|
||||
for i in 0..<unionRects.count {
|
||||
if rect.intersects(unionRects[i]) {
|
||||
begin = i * unionSize
|
||||
break
|
||||
}
|
||||
}
|
||||
for i in (0..<unionRects.count).reversed() {
|
||||
if rect.intersects(unionRects[i]) {
|
||||
end = min((i + 1) * unionSize, allItemAttributes.count)
|
||||
break
|
||||
}
|
||||
}
|
||||
for i in begin..<end {
|
||||
let attr = allItemAttributes[i]
|
||||
if rect.intersects(attr.frame) {
|
||||
attrs.append(attr)
|
||||
}
|
||||
}
|
||||
|
||||
return Array(attrs)
|
||||
}
|
||||
|
||||
public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
|
||||
let oldBounds = collectionView?.bounds
|
||||
if newBounds.width != oldBounds?.width {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
private extension NCMediaLayout {
|
||||
func shortestColumnIndex() -> Int {
|
||||
var index: Int = 0
|
||||
var shortestHeight = MAXFLOAT
|
||||
|
||||
for (idx, height) in columnHeights.enumerated() {
|
||||
if height < shortestHeight {
|
||||
shortestHeight = height
|
||||
index = idx
|
||||
}
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
func longestColumnIndex() -> Int {
|
||||
var index: Int = 0
|
||||
var longestHeight: Float = 0
|
||||
for (idx, height) in columnHeights.enumerated() {
|
||||
if height > longestHeight {
|
||||
longestHeight = height
|
||||
index = idx
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func invalidateIfNotEqual<T: Equatable>(_ oldValue: T, newValue: T) {
|
||||
if oldValue != newValue {
|
||||
invalidateLayout()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -180,7 +180,7 @@ extension NCCollectionViewCommon {
|
|||
NextcloudKit.shared.markE2EEFolder(fileId: metadata.fileId, delete: true) { _, error in
|
||||
if error == .success {
|
||||
NCManageDatabase.shared.deleteE2eEncryption(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", self.appDelegate.account, serverUrl))
|
||||
NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, serverUrlTo: nil, etag: nil, ocId: nil, fileId: nil, encrypted: false, richWorkspace: nil, account: metadata.account)
|
||||
NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, encrypted: false, account: metadata.account)
|
||||
NCManageDatabase.shared.setMetadataEncrypted(ocId: metadata.ocId, encrypted: false)
|
||||
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeStatusFolderE2EE, userInfo: ["serverUrl": metadata.serverUrl])
|
||||
|
|
|
@ -198,10 +198,6 @@ class NCGlobal: NSObject {
|
|||
//
|
||||
let fileNameRichWorkspace = "Readme.md"
|
||||
|
||||
// Extension
|
||||
//
|
||||
@objc let extensionPreview = "ico"
|
||||
|
||||
// ContentPresenter
|
||||
//
|
||||
@objc let dismissAfterSecond: TimeInterval = 4
|
||||
|
@ -352,6 +348,7 @@ class NCGlobal: NSObject {
|
|||
let notificationCenterReloadDataNCShare = "reloadDataNCShare"
|
||||
let notificationCenterCloseRichWorkspaceWebView = "closeRichWorkspaceWebView"
|
||||
let notificationCenterReloadAvatar = "reloadAvatar"
|
||||
let notificationCenterCreateMediaCacheEnded = "createMediaCacheEnded"
|
||||
|
||||
@objc let notificationCenterReloadDataSource = "reloadDataSource"
|
||||
let notificationCenterReloadDataSourceNetwork = "reloadDataSourceNetwork"
|
||||
|
@ -501,4 +498,8 @@ class NCGlobal: NSObject {
|
|||
let diagnosticProblemsBadResponse = "BAD_SERVER_RESPONSE"
|
||||
let diagnosticProblemsUploadServerError = "UploadError.SERVER_ERROR"
|
||||
|
||||
// MEDIA LAYOUT
|
||||
//
|
||||
let mediaLayoutRatio = "mediaLayoutRatio"
|
||||
let mediaLayoutSquare = "mediaLayoutSquare"
|
||||
}
|
||||
|
|
|
@ -35,41 +35,51 @@ import RealmSwift
|
|||
// MARK: -
|
||||
|
||||
private let limit: Int = 1000
|
||||
private var account: String = ""
|
||||
private var brandElementColor: UIColor?
|
||||
|
||||
enum ImageType {
|
||||
case placeholder
|
||||
case actual(_ image: UIImage)
|
||||
}
|
||||
private var totalSize: Int64 = 0
|
||||
|
||||
struct metadataInfo {
|
||||
var etag: String
|
||||
var date: NSDate
|
||||
var width: Int
|
||||
var height: Int
|
||||
}
|
||||
|
||||
private typealias ThumbnailLRUCache = LRUCache<String, ImageType>
|
||||
private lazy var cache: ThumbnailLRUCache = {
|
||||
return ThumbnailLRUCache(countLimit: limit)
|
||||
struct imageInfo {
|
||||
var image: UIImage?
|
||||
var size: CGSize?
|
||||
var date: Date
|
||||
}
|
||||
|
||||
private typealias ThumbnailImageLRUCache = LRUCache<String, imageInfo>
|
||||
private typealias ThumbnailSizeLRUCache = LRUCache<String, CGSize?>
|
||||
|
||||
private lazy var cacheImage: ThumbnailImageLRUCache = {
|
||||
return ThumbnailImageLRUCache(countLimit: limit)
|
||||
}()
|
||||
private lazy var cacheSize: ThumbnailSizeLRUCache = {
|
||||
return ThumbnailSizeLRUCache()
|
||||
}()
|
||||
private var metadatasInfo: [String: metadataInfo] = [:]
|
||||
private var metadatas: ThreadSafeArray<tableMetadata>?
|
||||
|
||||
var createMediaCacheInProgress: Bool = false
|
||||
let showAllPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload')"
|
||||
let showBothPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND (classFile == '\(NKCommon.TypeClassFile.image.rawValue)' OR classFile == '\(NKCommon.TypeClassFile.video.rawValue)') AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')"
|
||||
let showOnlyPredicateMediaString = "account == %@ AND serverUrl BEGINSWITH %@ AND classFile == %@ AND NOT (session CONTAINS[c] 'upload') AND NOT (livePhotoFile != '' AND classFile == '\(NKCommon.TypeClassFile.video.rawValue)')"
|
||||
|
||||
override private init() {}
|
||||
|
||||
func createMediaCache(account: String) {
|
||||
|
||||
guard account != self.account, !account.isEmpty else { return }
|
||||
self.account = account
|
||||
@objc func createMediaCache(account: String, withCacheSize: Bool) {
|
||||
if createMediaCacheInProgress {
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] ThumbnailLRUCache image process already in progress")
|
||||
return
|
||||
}
|
||||
createMediaCacheInProgress = true
|
||||
|
||||
self.metadatasInfo.removeAll()
|
||||
self.metadatas = nil
|
||||
self.metadatas = getMediaMetadatas(account: account)
|
||||
guard let metadatas = self.metadatas, !metadatas.isEmpty else { return }
|
||||
let ext = ".preview.ico"
|
||||
let manager = FileManager.default
|
||||
let resourceKeys = Set<URLResourceKey>([.nameKey, .pathKey, .fileSizeKey, .creationDateKey])
|
||||
|
@ -77,12 +87,17 @@ import RealmSwift
|
|||
var path: URL
|
||||
var ocIdEtag: String
|
||||
var date: Date
|
||||
var fileSize: Int
|
||||
var width: Int
|
||||
var height: Int
|
||||
}
|
||||
var files: [FileInfo] = []
|
||||
let startDate = Date()
|
||||
|
||||
metadatas.forEach { metadata in
|
||||
metadatasInfo[metadata.ocId] = metadataInfo(etag: metadata.etag, date: metadata.date)
|
||||
if let metadatas = metadatas {
|
||||
metadatas.forEach { metadata in
|
||||
metadatasInfo[metadata.ocId] = metadataInfo(etag: metadata.etag, date: metadata.date, width: metadata.width, height: metadata.height)
|
||||
}
|
||||
}
|
||||
|
||||
if let enumerator = manager.enumerator(at: URL(fileURLWithPath: NCUtilityFileSystem().directoryProviderStorage), includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles]) {
|
||||
|
@ -90,12 +105,24 @@ import RealmSwift
|
|||
let fileName = fileURL.lastPathComponent
|
||||
let ocId = fileURL.deletingLastPathComponent().lastPathComponent
|
||||
guard let resourceValues = try? fileURL.resourceValues(forKeys: resourceKeys),
|
||||
let size = resourceValues.fileSize,
|
||||
size > 0,
|
||||
let date = metadatasInfo[ocId]?.date,
|
||||
let etag = metadatasInfo[ocId]?.etag,
|
||||
fileName == etag + ext else { continue }
|
||||
files.append(FileInfo(path: fileURL, ocIdEtag: ocId + etag, date: date as Date))
|
||||
let fileSize = resourceValues.fileSize,
|
||||
fileSize > 0 else { continue }
|
||||
let width = metadatasInfo[ocId]?.width ?? 0
|
||||
let height = metadatasInfo[ocId]?.height ?? 0
|
||||
if withCacheSize {
|
||||
if let date = metadatasInfo[ocId]?.date,
|
||||
let etag = metadatasInfo[ocId]?.etag,
|
||||
fileName == etag + ext {
|
||||
files.append(FileInfo(path: fileURL, ocIdEtag: ocId + etag, date: date as Date, fileSize: fileSize, width: width, height: height))
|
||||
} else {
|
||||
let etag = fileName.replacingOccurrences(of: ".preview.ico", with: "")
|
||||
files.append(FileInfo(path: fileURL, ocIdEtag: ocId + etag, date: Date.distantPast, fileSize: fileSize, width: width, height: height))
|
||||
}
|
||||
} else if let date = metadatasInfo[ocId]?.date, let etag = metadatasInfo[ocId]?.etag, fileName == etag + ext {
|
||||
files.append(FileInfo(path: fileURL, ocIdEtag: ocId + etag, date: date as Date, fileSize: fileSize, width: width, height: height))
|
||||
} else {
|
||||
print("Nothing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,23 +132,37 @@ import RealmSwift
|
|||
print("Last date: \(lastDate)")
|
||||
}
|
||||
|
||||
cache.removeAllValues()
|
||||
cacheImage.removeAllValues()
|
||||
cacheSize.removeAllValues()
|
||||
var counter: Int = 0
|
||||
for file in files {
|
||||
counter += 1
|
||||
if counter > (limit - 100) { break }
|
||||
if !withCacheSize, counter > limit {
|
||||
break
|
||||
}
|
||||
autoreleasepool {
|
||||
if let image = UIImage(contentsOfFile: file.path.path) {
|
||||
cache.setValue(.actual(image), forKey: file.ocIdEtag)
|
||||
if counter < limit {
|
||||
cacheImage.setValue(imageInfo(image: image, size: image.size, date: file.date), forKey: file.ocIdEtag)
|
||||
totalSize = totalSize + Int64(file.fileSize)
|
||||
}
|
||||
if file.width == 0, file.height == 0 {
|
||||
cacheSize.setValue(image.size, forKey: file.ocIdEtag)
|
||||
}
|
||||
}
|
||||
}
|
||||
counter += 1
|
||||
}
|
||||
|
||||
let diffDate = Date().timeIntervalSinceReferenceDate - startDate.timeIntervalSinceReferenceDate
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("Counter process: \(cache.count)")
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("Counter cache image: \(cacheImage.count)")
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("Counter cache size: \(cacheSize.count)")
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("Total size images process: " + NCUtilityFileSystem().transformedSize(totalSize))
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("Time process: \(diffDate)")
|
||||
NextcloudKit.shared.nkCommonInstance.writeLog("--------- ThumbnailLRUCache image process ---------")
|
||||
|
||||
createMediaCacheInProgress = false
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCreateMediaCacheEnded)
|
||||
}
|
||||
|
||||
func initialMetadatas() -> ThreadSafeArray<tableMetadata>? {
|
||||
|
@ -129,24 +170,33 @@ import RealmSwift
|
|||
return self.metadatas
|
||||
}
|
||||
|
||||
func getMediaImage(ocId: String, etag: String) -> ImageType? {
|
||||
return cache.value(forKey: ocId + etag)
|
||||
func setMediaImage(ocId: String, etag: String, image: UIImage, date: Date) {
|
||||
cacheImage.setValue(imageInfo(image: image, size: image.size, date: date), forKey: ocId + etag)
|
||||
}
|
||||
|
||||
func setMediaImage(ocId: String, etag: String, image: ImageType) {
|
||||
cache.setValue(image, forKey: ocId + etag)
|
||||
func getMediaImage(ocId: String, etag: String) -> UIImage? {
|
||||
if let cache = cacheImage.value(forKey: ocId + etag) {
|
||||
return cache.image
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc func clearMediaCache() {
|
||||
self.metadatasInfo.removeAll()
|
||||
self.metadatas = nil
|
||||
cache.removeAllValues()
|
||||
func hasMediaImageEnoughSpace() -> Bool {
|
||||
return limit > cacheImage.count
|
||||
}
|
||||
|
||||
func setMediaSize(ocId: String, etag: String, size: CGSize) {
|
||||
cacheSize.setValue(size, forKey: ocId + etag)
|
||||
}
|
||||
|
||||
func getMediaSize(ocId: String, etag: String) -> CGSize? {
|
||||
return cacheSize.value(forKey: ocId + etag) ?? nil
|
||||
}
|
||||
|
||||
func getMediaMetadatas(account: String, predicate: NSPredicate? = nil) -> ThreadSafeArray<tableMetadata>? {
|
||||
guard let account = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) else { return nil }
|
||||
let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: account.urlBase, userId: account.userId) + account.mediaPath
|
||||
let predicateBoth = NSPredicate(format: showBothPredicateMediaString, account.account, startServerUrl)
|
||||
guard let tableAccount = NCManageDatabase.shared.getAccount(predicate: NSPredicate(format: "account == %@", account)) else { return nil }
|
||||
let startServerUrl = NCUtilityFileSystem().getHomeServer(urlBase: tableAccount.urlBase, userId: tableAccount.userId) + tableAccount.mediaPath
|
||||
let predicateBoth = NSPredicate(format: showBothPredicateMediaString, account, startServerUrl)
|
||||
return NCManageDatabase.shared.getMediaMetadatas(predicate: predicate ?? predicateBoth)
|
||||
}
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ extension NCEndToEndMetadata {
|
|||
object.key = encrypted.key
|
||||
object.initializationVector = initializationVector
|
||||
object.metadataKey = metadataKey
|
||||
object.metadataVersion = metadataVersion
|
||||
object.version = "\(metadataVersion)"
|
||||
object.mimeType = encrypted.mimetype
|
||||
object.serverUrl = serverUrl
|
||||
|
||||
|
@ -317,7 +317,7 @@ extension NCEndToEndMetadata {
|
|||
object.key = encrypted.key
|
||||
object.initializationVector = filedrop.initializationVector
|
||||
object.metadataKey = metadataKey
|
||||
object.metadataVersion = metadataVersion
|
||||
object.version = "\(metadataVersion)"
|
||||
object.mimeType = encrypted.mimetype
|
||||
object.serverUrl = serverUrl
|
||||
|
||||
|
@ -434,7 +434,7 @@ extension NCEndToEndMetadata {
|
|||
object.initializationVector = initializationVector
|
||||
object.metadataKey = metadataKey
|
||||
object.metadataKeyIndex = metadataKeyIndex
|
||||
object.metadataVersion = metadataVersion
|
||||
object.version = "\(metadataVersion)"
|
||||
object.mimeType = encrypted.mimetype
|
||||
object.serverUrl = serverUrl
|
||||
|
||||
|
|
|
@ -185,11 +185,11 @@ extension NCEndToEndMetadata {
|
|||
let e2eEncryptions = NCManageDatabase.shared.getE2eEncryptions(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", account, serverUrl))
|
||||
|
||||
for e2eEncryption in e2eEncryptions {
|
||||
if e2eEncryption.blob == "files" {
|
||||
if e2eEncryption.mimeType == "httpd/unix-directory" {
|
||||
folders[e2eEncryption.fileNameIdentifier] = e2eEncryption.fileName
|
||||
} else {
|
||||
let file = E2eeV20.Metadata.ciphertext.Files(authenticationTag: e2eEncryption.authenticationTag, filename: e2eEncryption.fileName, key: e2eEncryption.key, mimetype: e2eEncryption.mimeType, nonce: e2eEncryption.initializationVector)
|
||||
filesCodable.updateValue(file, forKey: e2eEncryption.fileNameIdentifier)
|
||||
} else if e2eEncryption.blob == "folders" {
|
||||
folders[e2eEncryption.fileNameIdentifier] = e2eEncryption.fileName
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,20 +234,20 @@ extension NCEndToEndMetadata {
|
|||
|
||||
let isDirectoryTop = utilityFileSystem.isDirectoryE2EETop(account: account, serverUrl: serverUrl)
|
||||
|
||||
func addE2eEncryption(fileNameIdentifier: String, filename: String, authenticationTag: String, key: String, initializationVector: String, metadataKey: String, mimetype: String, blob: String) {
|
||||
func addE2eEncryption(fileNameIdentifier: String, filename: String, authenticationTag: String, key: String, initializationVector: String, metadataKey: String, mimetype: String) {
|
||||
|
||||
if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND fileName == %@", account, fileNameIdentifier)) {
|
||||
|
||||
let object = tableE2eEncryption.init(account: account, ocIdServerUrl: ocIdServerUrl, fileNameIdentifier: fileNameIdentifier)
|
||||
|
||||
object.authenticationTag = authenticationTag
|
||||
object.blob = blob
|
||||
object.fileName = filename
|
||||
object.key = key
|
||||
object.initializationVector = initializationVector
|
||||
object.metadataKey = metadataKey
|
||||
object.mimeType = mimetype
|
||||
object.serverUrl = serverUrl
|
||||
object.version = NCGlobal.shared.e2eeVersionV20
|
||||
|
||||
// Write file parameter for decrypted on DB
|
||||
NCManageDatabase.shared.addE2eEncryption(object)
|
||||
|
@ -327,7 +327,7 @@ extension NCEndToEndMetadata {
|
|||
if let jsonText = String(data: data, encoding: .utf8) { print(jsonText) }
|
||||
let file = try JSONDecoder().decode(E2eeV20.Metadata.ciphertext.Files.self, from: data)
|
||||
print(file)
|
||||
addE2eEncryption(fileNameIdentifier: fileNameIdentifier, filename: file.filename, authenticationTag: file.authenticationTag, key: file.key, initializationVector: file.nonce, metadataKey: filedropKey, mimetype: file.mimetype, blob: "files")
|
||||
addE2eEncryption(fileNameIdentifier: fileNameIdentifier, filename: file.filename, authenticationTag: file.authenticationTag, key: file.key, initializationVector: file.nonce, metadataKey: filedropKey, mimetype: file.mimetype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -389,7 +389,7 @@ extension NCEndToEndMetadata {
|
|||
if let files = jsonCiphertextMetadata.files {
|
||||
print("\nFILES ---------------------------------\n")
|
||||
for file in files {
|
||||
addE2eEncryption(fileNameIdentifier: file.key, filename: file.value.filename, authenticationTag: file.value.authenticationTag, key: file.value.key, initializationVector: file.value.nonce, metadataKey: metadataKey, mimetype: file.value.mimetype, blob: "files")
|
||||
addE2eEncryption(fileNameIdentifier: file.key, filename: file.value.filename, authenticationTag: file.value.authenticationTag, key: file.value.key, initializationVector: file.value.nonce, metadataKey: metadataKey, mimetype: file.value.mimetype)
|
||||
|
||||
print("filename: \(file.value.filename)")
|
||||
print("fileNameIdentifier: \(file.key)")
|
||||
|
@ -401,7 +401,7 @@ extension NCEndToEndMetadata {
|
|||
if let folders = jsonCiphertextMetadata.folders {
|
||||
print("FOLDERS--------------------------------\n")
|
||||
for folder in folders {
|
||||
addE2eEncryption(fileNameIdentifier: folder.key, filename: folder.value, authenticationTag: metadata.authenticationTag, key: metadataKey, initializationVector: metadata.nonce, metadataKey: metadataKey, mimetype: "httpd/unix-directory", blob: "folders")
|
||||
addE2eEncryption(fileNameIdentifier: folder.key, filename: folder.value, authenticationTag: metadata.authenticationTag, key: metadataKey, initializationVector: metadata.nonce, metadataKey: metadataKey, mimetype: "httpd/unix-directory")
|
||||
|
||||
print("filename: \(folder.value)")
|
||||
print("fileNameIdentifier: \(folder.key)")
|
||||
|
|
|
@ -24,6 +24,9 @@ import NextcloudKit
|
|||
|
||||
class NCNetworkingE2EE: NSObject {
|
||||
|
||||
let e2EEApiVersion1 = "v1"
|
||||
let e2EEApiVersion2 = "v2"
|
||||
|
||||
func isInUpload(account: String, serverUrl: String) -> Bool {
|
||||
|
||||
let counter = NCManageDatabase.shared.getMetadatas(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND (status == %d OR status == %d)", account, serverUrl, NCGlobal.shared.metadataStatusWaitUpload, NCGlobal.shared.metadataStatusUploading)).count
|
||||
|
@ -40,11 +43,58 @@ class NCNetworkingE2EE: NSObject {
|
|||
|
||||
func getOptions() -> NKRequestOptions {
|
||||
|
||||
let version = NCGlobal.shared.capabilityE2EEApiVersion == "2.0" ? "v2" : "v1"
|
||||
let version = NCGlobal.shared.capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 ? e2EEApiVersion2 : e2EEApiVersion1
|
||||
return NKRequestOptions(version: version)
|
||||
}
|
||||
|
||||
func uploadMetadata(account: String, serverUrl: String, userId: String, addUserId: String? = nil, removeUserId: String? = nil) async -> NKError {
|
||||
// MARK: -
|
||||
|
||||
func getMetadata(fileId: String,
|
||||
e2eToken: String?,
|
||||
completion: @escaping (_ account: String, _ version: String?, _ e2eMetadata: String?, _ signature: String?, _ data: Data?, _ error: NKError) -> Void) {
|
||||
|
||||
switch NCGlobal.shared.capabilityE2EEApiVersion {
|
||||
case NCGlobal.shared.e2eeVersionV11, NCGlobal.shared.e2eeVersionV12:
|
||||
NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, options: NKRequestOptions(version: e2EEApiVersion1)) { account, e2eMetadata, signature, data, error in
|
||||
return completion(account, self.e2EEApiVersion1, e2eMetadata, signature, data, error)
|
||||
}
|
||||
case NCGlobal.shared.e2eeVersionV20:
|
||||
var options = NKRequestOptions(version: e2EEApiVersion2)
|
||||
NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, options: options) { account, e2eMetadata, signature, data, error in
|
||||
if error == .success {
|
||||
return completion(account, self.e2EEApiVersion2, e2eMetadata, signature, data, error)
|
||||
} else if error.errorCode == NCGlobal.shared.errorResourceNotFound {
|
||||
return completion(account, self.e2EEApiVersion2, e2eMetadata, signature, data, error)
|
||||
} else {
|
||||
options = NKRequestOptions(version: self.e2EEApiVersion1)
|
||||
NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, options: options) { account, e2eMetadata, signature, data, error in
|
||||
completion(account, self.e2EEApiVersion1, e2eMetadata, signature, data, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
completion("", "", nil, nil, nil, NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "version e2ee not available"))
|
||||
}
|
||||
}
|
||||
|
||||
func getMetadata(fileId: String,
|
||||
e2eToken: String?) async -> (account: String, version: String?, e2eMetadata: String?, signature: String?, data: Data?, error: NKError) {
|
||||
|
||||
await withUnsafeContinuation({ continuation in
|
||||
getMetadata(fileId: fileId, e2eToken: e2eToken) { account, version, e2eMetadata, signature, data, error in
|
||||
continuation.resume(returning: (account: account, version: version, e2eMetadata: e2eMetadata, signature: signature, data: data, error: error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func uploadMetadata(account: String,
|
||||
serverUrl: String,
|
||||
userId: String,
|
||||
addUserId: String? = nil,
|
||||
removeUserId: String? = nil,
|
||||
updateVersionV1V2: Bool = false) async -> NKError {
|
||||
|
||||
var addCertificate: String?
|
||||
var method = "POST"
|
||||
|
@ -70,11 +120,15 @@ class NCNetworkingE2EE: NSObject {
|
|||
|
||||
// METHOD
|
||||
//
|
||||
let resultsGetE2EEMetadata = await NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, options: NCNetworkingE2EE().getOptions())
|
||||
if resultsGetE2EEMetadata.error == .success {
|
||||
if updateVersionV1V2 {
|
||||
method = "PUT"
|
||||
} else if resultsGetE2EEMetadata.error.errorCode != NCGlobal.shared.errorResourceNotFound {
|
||||
return resultsGetE2EEMetadata.error
|
||||
} else {
|
||||
let resultsGetE2EEMetadata = await getMetadata(fileId: fileId, e2eToken: e2eToken)
|
||||
if resultsGetE2EEMetadata.error == .success {
|
||||
method = "PUT"
|
||||
} else if resultsGetE2EEMetadata.error.errorCode != NCGlobal.shared.errorResourceNotFound {
|
||||
return resultsGetE2EEMetadata.error
|
||||
}
|
||||
}
|
||||
|
||||
// UPLOAD METADATA
|
||||
|
@ -89,6 +143,7 @@ class NCNetworkingE2EE: NSObject {
|
|||
addUserId: addUserId,
|
||||
addCertificate: addCertificate,
|
||||
removeUserId: removeUserId)
|
||||
|
||||
guard uploadMetadataError == .success else {
|
||||
await unlock(account: account, serverUrl: serverUrl)
|
||||
return uploadMetadataError
|
||||
|
@ -101,28 +156,6 @@ class NCNetworkingE2EE: NSObject {
|
|||
return NKError()
|
||||
}
|
||||
|
||||
func downloadMetadata(account: String,
|
||||
serverUrl: String,
|
||||
urlBase: String,
|
||||
userId: String,
|
||||
fileId: String,
|
||||
e2eToken: String) async -> NKError {
|
||||
|
||||
let resultsGetE2EEMetadata = await NextcloudKit.shared.getE2EEMetadata(fileId: fileId, e2eToken: e2eToken, options: NCNetworkingE2EE().getOptions())
|
||||
guard resultsGetE2EEMetadata.error == .success, let e2eMetadata = resultsGetE2EEMetadata.e2eMetadata else {
|
||||
return resultsGetE2EEMetadata.error
|
||||
}
|
||||
|
||||
let resultsDecodeMetadataError = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: resultsGetE2EEMetadata.signature, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId)
|
||||
guard resultsDecodeMetadataError == .success else {
|
||||
// Client Diagnostic
|
||||
NCManageDatabase.shared.addDiagnostic(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)
|
||||
return resultsDecodeMetadataError
|
||||
}
|
||||
|
||||
return NKError()
|
||||
}
|
||||
|
||||
func uploadMetadata(account: String,
|
||||
serverUrl: String,
|
||||
ocIdServerUrl: String,
|
||||
|
@ -155,7 +188,34 @@ class NCNetworkingE2EE: NSObject {
|
|||
return NKError()
|
||||
}
|
||||
|
||||
func lock(account: String, serverUrl: String) async -> (fileId: String?, e2eToken: String?, error: NKError) {
|
||||
// MARK: -
|
||||
|
||||
func downloadMetadata(account: String,
|
||||
serverUrl: String,
|
||||
urlBase: String,
|
||||
userId: String,
|
||||
fileId: String,
|
||||
e2eToken: String) async -> NKError {
|
||||
|
||||
let resultsGetE2EEMetadata = await getMetadata(fileId: fileId, e2eToken: e2eToken)
|
||||
guard resultsGetE2EEMetadata.error == .success, let e2eMetadata = resultsGetE2EEMetadata.e2eMetadata else {
|
||||
return resultsGetE2EEMetadata.error
|
||||
}
|
||||
|
||||
let resultsDecodeMetadataError = NCEndToEndMetadata().decodeMetadata(e2eMetadata, signature: resultsGetE2EEMetadata.signature, serverUrl: serverUrl, account: account, urlBase: urlBase, userId: userId)
|
||||
guard resultsDecodeMetadataError == .success else {
|
||||
// Client Diagnostic
|
||||
NCManageDatabase.shared.addDiagnostic(account: account, issue: NCGlobal.shared.diagnosticIssueE2eeErrors)
|
||||
return resultsDecodeMetadataError
|
||||
}
|
||||
|
||||
return NKError()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func lock(account: String,
|
||||
serverUrl: String) async -> (fileId: String?, e2eToken: String?, error: NKError) {
|
||||
|
||||
var e2eToken: String?
|
||||
var e2eCounter = "1"
|
||||
|
|
|
@ -153,7 +153,7 @@ class NCNetworkingE2EECreateFolder: NSObject {
|
|||
}
|
||||
let metadata = NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: true)
|
||||
NCManageDatabase.shared.addMetadata(metadata)
|
||||
NCManageDatabase.shared.addDirectory(encrypted: true, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: true, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
|
||||
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCreateFolder, userInfo: ["ocId": ocId, "serverUrl": serverUrl, "account": account, "withPush": withPush])
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class NCNetworkingE2EEMarkFolder: NSObject {
|
|||
guard let metadata = NCManageDatabase.shared.addMetadata(NCManageDatabase.shared.convertFileToMetadata(file, isDirectoryE2EE: false)) else {
|
||||
return NKError(errorCode: NCGlobal.shared.errorUnexpectedResponseFromDB, errorDescription: "_e2e_error_")
|
||||
}
|
||||
NCManageDatabase.shared.addDirectory(encrypted: true, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: true, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: serverUrlFileName, account: metadata.account)
|
||||
NCManageDatabase.shared.deleteE2eEncryption(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, serverUrlFileName))
|
||||
if NCGlobal.shared.capabilityE2EEApiVersion == NCGlobal.shared.e2eeVersionV20 {
|
||||
NCManageDatabase.shared.updateCounterE2eMetadata(account: account, ocIdServerUrl: metadata.ocId, counter: 0)
|
||||
|
|
|
@ -113,19 +113,21 @@ extension NCNetworking {
|
|||
}) { _, etag, date, length, allHeaderFields, afError, error in
|
||||
|
||||
var error = error
|
||||
self.downloadRequest.removeValue(forKey: fileNameLocalPath)
|
||||
|
||||
var dateLastModified: NSDate?
|
||||
if let downloadTask = downloadTask {
|
||||
if let header = allHeaderFields, let dateString = header["Last-Modified"] as? String {
|
||||
dateLastModified = NextcloudKit.shared.nkCommonInstance.convertDate(dateString, format: "EEE, dd MMM y HH:mm:ss zzz")
|
||||
self.downloadRequest.removeValue(forKey: fileNameLocalPath)
|
||||
// this delay was added because for small file the "taskHandler: { task" is not called, so this part of code is not executed
|
||||
NextcloudKit.shared.nkCommonInstance.backgroundQueue.asyncAfter(deadline: .now() + 0.5) {
|
||||
if let downloadTask = downloadTask {
|
||||
if let header = allHeaderFields, let dateString = header["Last-Modified"] as? String {
|
||||
dateLastModified = NextcloudKit.shared.nkCommonInstance.convertDate(dateString, format: "EEE, dd MMM y HH:mm:ss zzz")
|
||||
}
|
||||
if afError?.isExplicitlyCancelledError ?? false {
|
||||
error = NKError(errorCode: NCGlobal.shared.errorRequestExplicityCancelled, errorDescription: "error request explicity cancelled")
|
||||
}
|
||||
self.downloadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, fileNameLocalPath: fileNameLocalPath, task: downloadTask, error: error)
|
||||
}
|
||||
if afError?.isExplicitlyCancelledError ?? false {
|
||||
error = NKError(errorCode: NCGlobal.shared.errorRequestExplicityCancelled, errorDescription: "error request explicity cancelled")
|
||||
}
|
||||
self.downloadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, fileNameLocalPath: fileNameLocalPath, task: downloadTask, error: error)
|
||||
completion(afError, error)
|
||||
}
|
||||
completion(afError, error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,13 +53,12 @@ extension NCNetworking {
|
|||
NCManageDatabase.shared.addMetadata(tableMetadata.init(value: metadataFolder))
|
||||
|
||||
// Update directory
|
||||
NCManageDatabase.shared.addDirectory(encrypted: metadataFolder.e2eEncrypted, favorite: metadataFolder.favorite, ocId: metadataFolder.ocId, fileId: metadataFolder.fileId, etag: metadataFolder.etag, permissions: metadataFolder.permissions, serverUrl: serverUrl, account: metadataFolder.account)
|
||||
NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, richWorkspace: metadataFolder.richWorkspace, account: metadataFolder.account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: metadataFolder.e2eEncrypted, favorite: metadataFolder.favorite, ocId: metadataFolder.ocId, fileId: metadataFolder.fileId, etag: metadataFolder.etag, permissions: metadataFolder.permissions, richWorkspace: metadataFolder.richWorkspace, serverUrl: serverUrl, account: metadataFolder.account)
|
||||
|
||||
// Update sub directories NO Update richWorkspace
|
||||
for metadata in metadatasFolder {
|
||||
let serverUrl = metadata.serverUrl + "/" + metadata.fileName
|
||||
NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrl, account: account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: serverUrl, account: account)
|
||||
}
|
||||
|
||||
#if !EXTENSION
|
||||
|
@ -255,7 +254,7 @@ extension NCNetworking {
|
|||
if error == .success {
|
||||
if let metadata = metadataFolder {
|
||||
NCManageDatabase.shared.addMetadata(metadata)
|
||||
NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: fileNameFolderUrl, account: account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: fileNameFolderUrl, account: account)
|
||||
}
|
||||
if let metadata = NCManageDatabase.shared.getMetadataFromOcId(metadataFolder?.ocId) {
|
||||
NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCreateFolder, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl, "account": metadata.account, "withPush": withPush])
|
||||
|
@ -517,7 +516,7 @@ extension NCNetworking {
|
|||
let serverUrl = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName)
|
||||
let serverUrlTo = self.utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: fileNameNew)
|
||||
if let directory = NCManageDatabase.shared.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, metadata.serverUrl)) {
|
||||
NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, serverUrlTo: serverUrlTo, etag: "", ocId: nil, fileId: nil, encrypted: directory.e2eEncrypted, richWorkspace: nil, account: metadata.account)
|
||||
NCManageDatabase.shared.setDirectory(serverUrl: serverUrl, serverUrlTo: serverUrlTo, etag: "", encrypted: directory.e2eEncrypted, account: metadata.account)
|
||||
}
|
||||
} else {
|
||||
if (metadata.fileName as NSString).pathExtension != (fileNameNew as NSString).pathExtension {
|
||||
|
@ -749,7 +748,7 @@ extension NCNetworking {
|
|||
// Update sub directories
|
||||
for folder in metadatasFolder {
|
||||
let serverUrl = folder.serverUrl + "/" + folder.fileName
|
||||
NCManageDatabase.shared.addDirectory(encrypted: folder.e2eEncrypted, favorite: folder.favorite, ocId: folder.ocId, fileId: folder.fileId, etag: nil, permissions: folder.permissions, serverUrl: serverUrl, account: account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: folder.e2eEncrypted, favorite: folder.favorite, ocId: folder.ocId, fileId: folder.fileId, permissions: folder.permissions, serverUrl: serverUrl, account: account)
|
||||
}
|
||||
|
||||
NCManageDatabase.shared.addMetadatas(metadatas)
|
||||
|
|
|
@ -142,7 +142,7 @@ class NCRecent: NCCollectionViewCommon {
|
|||
// Update sub directories
|
||||
for metadata in metadatasFolder {
|
||||
let serverUrl = metadata.serverUrl + "/" + metadata.fileName
|
||||
NCManageDatabase.shared.addDirectory(encrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, etag: nil, permissions: metadata.permissions, serverUrl: serverUrl, account: account)
|
||||
NCManageDatabase.shared.addDirectory(e2eEncrypted: metadata.e2eEncrypted, favorite: metadata.favorite, ocId: metadata.ocId, fileId: metadata.fileId, permissions: metadata.permissions, serverUrl: serverUrl, account: account)
|
||||
}
|
||||
// Add metadatas
|
||||
NCManageDatabase.shared.addMetadatas(metadatas)
|
||||
|
|
|
@ -375,7 +375,7 @@
|
|||
|
||||
[[NCAutoUpload shared] alignPhotoLibraryWithViewController:self];
|
||||
|
||||
[[NCImageCache shared] clearMediaCache];
|
||||
[[NCImageCache shared] createMediaCacheWithAccount:appDelegate.account withCacheSize:true];
|
||||
|
||||
[[NCActivityIndicator shared] stop];
|
||||
[self calculateSize];
|
||||
|
|
|
@ -28,6 +28,18 @@ import KeychainAccess
|
|||
|
||||
let keychain = Keychain(service: "com.nextcloud.keychain")
|
||||
|
||||
var showDescription: Bool {
|
||||
get {
|
||||
if let value = try? keychain.get("showDescription"), let result = Bool(value) {
|
||||
return result
|
||||
}
|
||||
return true
|
||||
}
|
||||
set {
|
||||
keychain["showDescription"] = String(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
var typeFilterScanDocument: NCGlobal.TypeFilterScanDocument {
|
||||
get {
|
||||
if let rawValue = try? keychain.get("ScanDocumentTypeFilter"), let value = NCGlobal.TypeFilterScanDocument(rawValue: rawValue) {
|
||||
|
@ -267,15 +279,27 @@ import KeychainAccess
|
|||
}
|
||||
}
|
||||
|
||||
var mediaItemForLine: Int {
|
||||
var mediaColumnCount: Int {
|
||||
get {
|
||||
if let value = try? keychain.get("itemForLine"), let result = Int(value) {
|
||||
if let value = try? keychain.get("mediaColumnCount"), let result = Int(value) {
|
||||
return result
|
||||
}
|
||||
return 3
|
||||
}
|
||||
set {
|
||||
keychain["itemForLine"] = String(newValue)
|
||||
keychain["mediaColumnCount"] = String(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
var mediaTypeLayout: String {
|
||||
get {
|
||||
if let value = try? keychain.get("mediaTypeLayout") {
|
||||
return value
|
||||
}
|
||||
return NCGlobal.shared.mediaLayoutRatio
|
||||
}
|
||||
set {
|
||||
keychain["mediaTypeLayout"] = String(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,20 +344,6 @@ import KeychainAccess
|
|||
|
||||
// MARK: -
|
||||
|
||||
private func migrate(key: String) {
|
||||
let keychainOLD = Keychain(service: "Crypto Cloud")
|
||||
if let value = keychainOLD[key], !value.isEmpty {
|
||||
keychain[key] = value
|
||||
keychainOLD[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc func removeAll() {
|
||||
try? keychain.removeAll()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc func getPassword(account: String) -> String {
|
||||
let key = "password" + account
|
||||
migrate(key: key)
|
||||
|
@ -515,4 +525,18 @@ import KeychainAccess
|
|||
setPushNotificationDeviceIdentifier(account: account, deviceIdentifier: nil)
|
||||
setPushNotificationDeviceIdentifierSignature(account: account, deviceIdentifierSignature: nil)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func migrate(key: String) {
|
||||
let keychainOLD = Keychain(service: "Crypto Cloud")
|
||||
if let value = keychainOLD[key], !value.isEmpty {
|
||||
keychain[key] = value
|
||||
keychainOLD[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc func removeAll() {
|
||||
try? keychain.removeAll()
|
||||
}
|
||||
}
|
||||
|
|
Двоичные данные
iOSClient/Supporting Files/af.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/af.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/an.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/an.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/ar.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/ar.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/ast.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/ast.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/az.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/az.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/be.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/be.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/br.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/br.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/bs.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/bs.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/ca.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/ca.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/da.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/da.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/de.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/de.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/el.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/el.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/en-GB.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/en-GB.lproj/Localizable.strings
Двоичный файл не отображается.
|
@ -562,6 +562,8 @@
|
|||
"_file_saved_cameraroll_" = "File saved in photo album";
|
||||
"_directory_on_top_yes_" = "✓ Folders on top";
|
||||
"_directory_on_top_no_" = "Folders on top";
|
||||
"_show_description_" = "Show description";
|
||||
"_no_description_available_" = "No description available for this folder";
|
||||
"_folder_automatic_upload_" = "Folder for \"Auto upload\"";
|
||||
"_search_no_record_found_" = "No result";
|
||||
"_search_in_progress_" = "Search in progress …";
|
||||
|
@ -967,6 +969,8 @@
|
|||
"_selected_photo_" = "selected photo";
|
||||
"_selected_photos_" = "selected photos";
|
||||
"_delete_selected_photos_" = "Delete selected photos";
|
||||
"_media_square_" = "Square photo grid";
|
||||
"_media_ratio_" = "Aspect ratio grid";
|
||||
|
||||
// Video
|
||||
"_select_trace_" = "Select the trace";
|
||||
|
|
Двоичные данные
iOSClient/Supporting Files/eo.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/eo.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-419.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-419.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-AR.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-AR.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-CL.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-CL.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-CO.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-CO.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-CR.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-CR.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-DO.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-DO.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-EC.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-EC.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-GT.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-GT.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-HN.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-HN.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-MX.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-MX.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-NI.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-NI.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-PA.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-PA.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-PE.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-PE.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-PR.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-PR.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-PY.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-PY.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-SV.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-SV.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es-UY.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es-UY.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/es.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/es.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/et_EE.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/et_EE.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/eu.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/eu.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/fa.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/fa.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/fi-FI.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/fi-FI.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/fo.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/fo.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/fr.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/fr.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/gd.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/gd.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/gl.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/gl.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/he.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/he.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/hi_IN.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/hi_IN.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/hr.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/hr.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/hsb.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/hsb.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/hu.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/hu.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/hy.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/hy.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/ia.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/ia.lproj/Localizable.strings
Двоичный файл не отображается.
Двоичные данные
iOSClient/Supporting Files/id.lproj/Localizable.strings
Двоичные данные
iOSClient/Supporting Files/id.lproj/Localizable.strings
Двоичный файл не отображается.
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче