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:
Marino Faggiana 2024-03-08 08:21:32 +01:00 коммит произвёл GitHub
Родитель 0f24e373b1
Коммит 62c79619fc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
160 изменённых файлов: 1670 добавлений и 1040 удалений

Двоичные данные
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()
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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